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

/**
 * \file autodiffhelper.hh
 * \brief Helper for the autodiff library
 */

#pragma once
#include <autodiff/forward/dual.hpp>
#include <autodiff/forward/dual/eigen.hpp>

namespace Ikarus::utils {

/**
 * \brief Computes the Hessian matrix for each parameter of a given function.
 * \ingroup  utils
 * The Hessian matrix represents the second-order partial derivatives of the function with respect to the specified
 * variables.
 *
 * \tparam Fun The type of the function to be differentiated.
 * \tparam Vars The types of the variables with respect to which the Hessian is computed.
 * \tparam Args The types of the arguments passed to the function.
 * \tparam U The type representing the result of the function evaluation.
 * \tparam G The type representing the gradient of the function.
 * \tparam H The type representing the Hessian matrix.
 * \param f The function to be differentiated.
 * \param wrt The variables with respect to which the Hessian is computed.
 * \param at The values at which the Hessian is evaluated.
 * \param u The result of the function evaluation.
 * \param g The gradient of the function.
 * \param h The Hessian matrix (output).
 */
template <typename Fun, typename... Vars, typename... Args, typename U, typename G, typename H>
void hessianN(const Fun& f, const autodiff::Wrt<Vars...>& wrt, const autodiff::At<Args...>& at, U& u,
              std::array<G, U::RowsAtCompileTime>& g, std::array<H, U::RowsAtCompileTime>& h) {
  static_assert(sizeof...(Vars) >= 1);
  static_assert(sizeof...(Args) >= 1);

  auto fEntry = [&](auto& I) { return [&](const auto&) { return std::apply(f, at.args)[I]; }; };
  for (int i = 0; i < U::RowsAtCompileTime; ++i)
    hessian(fEntry(i), wrt, at, u[i], g[i], h[i]);
}
} // namespace Ikarus::utils
