// SPDX-FileCopyrightText: 2021-2024 The Ikarus Developers mueller@ibb.uni-stuttgart.de
// SPDX-License-Identifier: LGPL-3.0-or-later

/*!
 * \file pathfollowing.inl
 * \brief Implementation of the run function
 * @ingroup  controlroutines
 */

#pragma once


#include <Eigen/Core>

#include <ikarus/controlroutines/adaptivestepsizing.hh>
#include <ikarus/controlroutines/controlinfos.hh>
#include <ikarus/controlroutines/pathfollowingfunctions.hh>
#include <ikarus/utils/nonlinearoperator.hh>
#include <ikarus/solver/nonlinearsolver/newtonraphsonwithscalarsubsidiaryfunction.hh>
#include <ikarus/utils/observer/observer.hh>
#include <ikarus/utils/observer/observermessages.hh>

namespace Ikarus {

  template <typename NonLinearSolver, typename PathFollowingType,
            typename AdaptiveStepSizing>requires (Impl::checkPathFollowingTemplates<NonLinearSolver,PathFollowingType,AdaptiveStepSizing>())
    ControlInformation PathFollowing<NonLinearSolver,PathFollowingType,AdaptiveStepSizing>::run() {
      ControlInformation info;
      auto& nonOp = nonLinearSolver->nonLinearOperator();
      this->notify(ControlMessages::CONTROL_STARTED, pathFollowingType_.name());

      SubsidiaryArgs subsidiaryArgs;

      info.totalIterations    = 0;
      subsidiaryArgs.stepSize = stepSize_;
      subsidiaryArgs.DD.resizeLike(nonOp.firstParameter());
      subsidiaryArgs.DD.setZero();

      /// Initializing solver
      this->notify(ControlMessages::STEP_STARTED, 0, subsidiaryArgs.stepSize);
      auto subsidiaryFunctionPtr = [&](auto&& args) { return pathFollowingType_(args); };
      pathFollowingType_.initialPrediction(nonOp, subsidiaryArgs);
      auto solverInfo = nonLinearSolver->solve(subsidiaryFunctionPtr, subsidiaryArgs);
      info.solverInfos.push_back(solverInfo);
      info.totalIterations += solverInfo.iterations;
      if (not solverInfo.success) return info;
      this->notify(ControlMessages::SOLUTION_CHANGED);
      this->notify(ControlMessages::STEP_ENDED);

      /// Calculate predictor for a particular step
      for (int ls = 1; ls < steps_; ++ls) {
        subsidiaryArgs.currentStep = ls;

        adaptiveStepSizing(solverInfo, subsidiaryArgs, nonOp);

        this->notify(ControlMessages::STEP_STARTED, subsidiaryArgs.currentStep, subsidiaryArgs.stepSize);

        pathFollowingType_.intermediatePrediction(nonOp, subsidiaryArgs);

        solverInfo = nonLinearSolver->solve(subsidiaryFunctionPtr, subsidiaryArgs);

        info.solverInfos.push_back(solverInfo);
        info.totalIterations += solverInfo.iterations;
        if (not solverInfo.success) return info;
        this->notify(ControlMessages::SOLUTION_CHANGED);
        this->notify(ControlMessages::STEP_ENDED);
      }

      this->notify(ControlMessages::CONTROL_ENDED, info.totalIterations, pathFollowingType_.name());
      info.success = true;
      return info;
    }
}  // namespace Ikarus
