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

/**
 * \file pythonautodiffdefinitions.hh
 * \brief Implementation of forwarding autodiff types from python to c++ and vice versa
 */

#pragma once
#include <cstddef>
#include <Python.h>

#include <dune/fufem/dunepython.hh>

#include <autodiff/forward/real/real.hpp>

namespace Python {
  // *****************************************************************************
  // specializations of Conversion that do the PyObject* <-> C++-type conversion
  // *****************************************************************************

  /**
   * @brief Conversion specialization for autodiff::Real type.
   * @tparam order The order of autodiff::Real.
   * @tparam T The underlying type of autodiff::Real.
   */
  template <std::size_t order, class T>
  struct Conversion<autodiff::Real<order, T>> {
    enum { useDefaultConstructorConversion = true };

    /**
     * @brief Convert autodiff::Real to PyObject*.
     * @param list Python list object to be populated.
     * @param v The autodiff::Real object to be converted.
     */
    static void toC(PyObject* list, autodiff::Real<order, T>& v) {
      auto rlist = Reference(Imp::inc(list));
      // Reference is needed to enable the ".get()" function and "Imp::inc" is
      // needed since Reference owns the PyObject pointer and decrements it at the end of the scope
      // Imp::inc artificially increases the reference counter by one.
      // When we return from this function, these two cancel out and the PyObject* is as before

      auto wF = Callable(rlist.get("__getitem__"));
      for (std::size_t i = 0; i < order + 1; ++i)
        v[i] = PyFloat_AsDouble(wF(i));
    }

    /**
     * @brief Convert PyObject* to autodiff::Real.
     * @param v The autodiff::Real object to be populated.
     * @return PyObject* representing the autodiff::Real object.
     */
    static PyObject* toPy(const autodiff::Real<order, T>& v) {
      auto pyMain           = Python::main();
      Python::Module module = pyMain.import("autodiff");

      auto real1stClass = module.get("real1st");
      auto real1st      = Callable(Imp::inc(real1stClass))();
      auto wF           = Callable(Imp::inc(real1st).get("__setitem__"));
      for (std::size_t i = 0; i < order + 1; ++i)
        wF(i, v[i]);  // real1st.__setitem__(i,v[i]);

      return real1st;
    }
  };
}  // namespace Python
