#pragma once

#include <iostream>

#include "autoopt/optimization_problem.hpp"

namespace autoopt {

template <typename T>
struct btls_parameters {
  T step_decrease = T{0.5};
  T step_increase = T{1.2};
  T sufficient_decrease = T{1e-2};
  T tolerance = T{1e-10};
  size_t max_iters = 2000;
};

template <typename T>
void btls(optimization_problem<T>& problem,
          const btls_parameters<T>& params = btls_parameters<T>()) {
  Eigen::VectorX<T>& x = problem.x();
  T step_size = T{1.0};

  for (size_t iter = 0; iter < params.max_iters; ++iter) {
    T obj_value = problem.objective(x);

    Eigen::VectorX<T> grad = problem.gradient(x);

    Eigen::MatrixX<T> hess = problem.hessian(x);
    Eigen::VectorX<T> step_dir = -hess.ldlt().solve(grad).normalized();

    // Eigen::VectorX<T> step_dir = -grad.normalized();

    auto decrease_condition = [&] {
      T new_obj = problem.objective(x + step_size * step_dir);
      return std::isnan(new_obj) ||
             (new_obj > obj_value - std::abs(params.sufficient_decrease *
                                             step_size * grad.dot(step_dir)));
    };

    while (decrease_condition()) {
      step_size *= params.step_decrease;
    }
    x += step_size * step_dir;

    step_size = step_size * params.step_increase;

    if (step_size < params.tolerance) {
      break;
    }
  }

  problem.x() = x;
}

}  // namespace autoopt