//
// Copyright (c) Lander Vanroye, KU Leuven.
// This file is part of fatrop.
//
// This file contains work derived from Ipopt (https://github.com/coin-or/Ipopt),
// Copyright (C) 2004, 2010 International Business Machines and others.
// Ipopt is licensed under the Eclipse Public License 2.0 (EPL-2.0).
//
// This file is licensed under the Eclipse Public License 2.0.
// See LICENSE-EPL-2.0.txt for the full license text.
//

#ifndef __fatrop_ip_algorithm_ip_algorithm_hxx__
#define __fatrop_ip_algorithm_ip_algorithm_hxx__
#include "fatrop/ip_algorithm/ip_algorithm.hpp"
#include "fatrop/ip_algorithm/ip_convergence_check.hpp"
#include "fatrop/ip_algorithm/ip_data.hpp"
#include "fatrop/ip_algorithm/ip_eq_mult_initializer.hpp"
#include "fatrop/ip_algorithm/ip_initializer.hpp"
#include "fatrop/ip_algorithm/ip_iteration_output.hpp"
#include "fatrop/ip_algorithm/ip_linesearch.hpp"
#include "fatrop/ip_algorithm/ip_mu_update.hpp"
#include "fatrop/ip_algorithm/ip_search_dir.hpp"
#include "fatrop/ip_algorithm/ip_timings.hpp"
#include "fatrop/nlp/nlp.hpp"
#include "fatrop/ocp/type.hpp"
namespace fatrop
{
    template <typename ProblemType>
    IpAlgorithm<ProblemType>::IpAlgorithm(const IpSearchDirSp &search_dir,
                                          const IpLineSearchSp &linesearch,
                                          const IpInitializerSp &initializer,
                                          const IpMuUpdateSp &mu_update,
                                          const IpEqMultInitializerSp &eq_mult_initializer,
                                          const IpConvergenceCheckSp &convergence_check,
                                          const IpIterationOutputSp &iteration_output,
                                          const IpDataSp &ip_data)
        : search_dir_(search_dir), linesearch_(linesearch), initializer_(initializer),
          mu_update_(mu_update), eq_mult_initializer_(eq_mult_initializer),
          convergence_check_(convergence_check), iteration_output_(iteration_output),
          ip_data_(ip_data)
    {
    }

    template <typename ProblemType> void IpAlgorithm<ProblemType>::reset(bool is_resto /* = false*/)
    {
        // todo who resets the ipdata?
        search_dir_->reset();
        linesearch_->reset();
        initializer_->reset();
        mu_update_->reset();
        eq_mult_initializer_->reset();
        convergence_check_->reset();
        ip_data_->reset(is_resto);
        // Note: IpIterationOutput might not need a reset method
    }

    template <typename ProblemType>
    IpSolverReturnFlag IpAlgorithm<ProblemType>::optimize(bool is_resto)
    {
        reset(is_resto);
        ip_data_->timing_statistics().full_algorithm.start();
        {
            ScopedTimer _t(ip_data_->timing_statistics().initialization,
                           ip_data_->timing_statistics());
            initializer_->initialize();
        }
        IpSolverReturnFlag retval = IpSolverReturnFlag::Unknown;
        IpConvergenceStatus conv_status = convergence_check_->check_converged();

        iteration_output_->print_header();

        while (conv_status == IpConvergenceStatus::Continue)
        {
            ip_data_->get_nlp()->callback(*ip_data_);
            iteration_output_->output_current_iteration();
            mu_update_->update_barrier_parameter();
            {
                LinsolReturnFlag sd_ret = search_dir_->compute_search_dir();
                if (sd_ret == LinsolReturnFlag::UNKNOWN || sd_ret == LinsolReturnFlag::NAN_SOLUTION)
                    return IpSolverReturnFlag::ErrorInStepComputation;
            }
            bool success = linesearch_->find_acceptable_trial_point();
            if(!success) return IpSolverReturnFlag::LineSearchFailed;
            ip_data_->accept_trial_iterate();
            conv_status = convergence_check_->check_converged();
            ip_data_->set_iteration_number(ip_data_->iteration_number() + 1);
        }
        iteration_output_->output_current_iteration();
        if (conv_status == IpConvergenceStatus::Converged)
            retval = IpSolverReturnFlag::Success;
        else if (conv_status == IpConvergenceStatus::ConvergedToAcceptablePoint)
            retval = IpSolverReturnFlag::StopAtAcceptablePoint;
        else if (conv_status == IpConvergenceStatus::MaxIterExceeded)
            retval = IpSolverReturnFlag::MaxIterExceeded;
        else if (conv_status == IpConvergenceStatus::RestoFail)
            retval = IpSolverReturnFlag::LocalInfeasibility;
        ip_data_->timing_statistics().full_algorithm.pause();
        return retval;
    }
    template <typename ProblemType>
    const ProblemInfo<ProblemType> &IpAlgorithm<ProblemType>::info() const
    {
        return ip_data_->current_iterate().info();
    }

    template <typename ProblemType>
    const VecRealView &IpAlgorithm<ProblemType>::solution_primal() const
    {
        return ip_data_->current_iterate().primal_x();
    }
    template <typename ProblemType>
    const VecRealView &IpAlgorithm<ProblemType>::solution_dual() const
    {
        return ip_data_->current_iterate().dual_eq();
    }

} // namespace fatrop

#endif //__fatrop_ip_algorithm_ip_data_hxx__
