//
// Copyright (c) 2014-2023 CNRS INRIA
//

#ifndef __eigenpy_eigen_from_python_hpp__
#define __eigenpy_eigen_from_python_hpp__

#include "eigenpy/fwd.hpp"
#include "eigenpy/eigen-allocator.hpp"
#include "eigenpy/numpy-type.hpp"
#include "eigenpy/scalar-conversion.hpp"

namespace eigenpy {

template <typename EigenType,
          typename BaseType = typename get_eigen_base_type<EigenType>::type>
struct expected_pytype_for_arg {};

template <typename MatType>
struct expected_pytype_for_arg<MatType, Eigen::MatrixBase<MatType>> {
  static PyTypeObject const* get_pytype() {
    PyTypeObject const* py_type = eigenpy::getPyArrayType();
    return py_type;
  }
};

}  // namespace eigenpy

namespace boost {
namespace python {
namespace converter {

template <typename Scalar, int Rows, int Cols, int Options, int MaxRows,
          int MaxCols>
struct expected_pytype_for_arg<
    Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols>>
    : eigenpy::expected_pytype_for_arg<
          Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols>> {};

}  // namespace converter
}  // namespace python
}  // namespace boost

namespace eigenpy {
namespace details {
template <typename MatType, bool is_const = boost::is_const<MatType>::value>
struct copy_if_non_const {
  static void run(const Eigen::MatrixBase<MatType>& input,
                  PyArrayObject* pyArray) {
    EigenAllocator<MatType>::copy(input, pyArray);
  }
};

template <typename MatType>
struct copy_if_non_const<const MatType, true> {
  static void run(const Eigen::MatrixBase<MatType>& /*input*/,
                  PyArrayObject* /*pyArray*/) {}
};

#if EIGEN_VERSION_AT_LEAST(3, 2, 0)

template <typename _RefType>
struct referent_storage_eigen_ref {
  typedef _RefType RefType;
  typedef typename get_eigen_plain_type<RefType>::type PlainObjectType;
  typedef typename ::eigenpy::aligned_storage<
      ::boost::python::detail::referent_size<RefType&>::value>::type
      AlignedStorage;

  referent_storage_eigen_ref()
      : pyArray(NULL),
        plain_ptr(NULL),
        ref_ptr(reinterpret_cast<RefType*>(ref_storage.bytes)) {}

  referent_storage_eigen_ref(const RefType& ref, PyArrayObject* pyArray,
                             PlainObjectType* plain_ptr = NULL)
      : pyArray(pyArray),
        plain_ptr(plain_ptr),
        ref_ptr(reinterpret_cast<RefType*>(ref_storage.bytes)) {
    Py_INCREF(pyArray);
    new (ref_storage.bytes) RefType(ref);
  }

  ~referent_storage_eigen_ref() {
    if (plain_ptr != NULL && PyArray_ISWRITEABLE(pyArray))
      copy_if_non_const<PlainObjectType>::run(*plain_ptr, pyArray);

    Py_DECREF(pyArray);

    if (plain_ptr != NULL) plain_ptr->~PlainObjectType();

    ref_ptr->~RefType();
  }

  AlignedStorage ref_storage;
  PyArrayObject* pyArray;
  PlainObjectType* plain_ptr;
  RefType* ref_ptr;
};
#endif

}  // namespace details
}  // namespace eigenpy

namespace boost {
namespace python {
namespace detail {
#if EIGEN_VERSION_AT_LEAST(3, 2, 0)
template <typename MatType, int Options, typename Stride>
struct referent_storage<Eigen::Ref<MatType, Options, Stride>&> {
  typedef Eigen::Ref<MatType, Options, Stride> RefType;
  typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
  typedef typename ::eigenpy::aligned_storage<
      referent_size<StorageType&>::value>::type type;
};

template <typename MatType, int Options, typename Stride>
struct referent_storage<const Eigen::Ref<const MatType, Options, Stride>&> {
  typedef Eigen::Ref<const MatType, Options, Stride> RefType;
  typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
  typedef typename ::eigenpy::aligned_storage<
      referent_size<StorageType&>::value>::type type;
};
#endif
}  // namespace detail
}  // namespace python
}  // namespace boost

namespace boost {
namespace python {
namespace converter {

#define EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(type)                       \
  typedef ::eigenpy::rvalue_from_python_data<type> Base;                 \
                                                                         \
  rvalue_from_python_data(rvalue_from_python_stage1_data const& _stage1) \
      : Base(_stage1) {}                                                 \
                                                                         \
  rvalue_from_python_data(void* convertible) : Base(convertible){};

template <typename Scalar, int Rows, int Cols, int Options, int MaxRows,
          int MaxCols>
struct rvalue_from_python_data<
    Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> const&>
    : ::eigenpy::rvalue_from_python_data<
          Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> const&> {
  typedef Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> T;
  EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(T const&)
};

template <typename Derived>
struct rvalue_from_python_data<Eigen::MatrixBase<Derived> const&>
    : ::eigenpy::rvalue_from_python_data<Derived const&> {
  EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(Derived const&)
};

template <typename Derived>
struct rvalue_from_python_data<Eigen::EigenBase<Derived> const&>
    : ::eigenpy::rvalue_from_python_data<Derived const&> {
  EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(Derived const&)
};

template <typename Derived>
struct rvalue_from_python_data<Eigen::PlainObjectBase<Derived> const&>
    : ::eigenpy::rvalue_from_python_data<Derived const&> {
  EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(Derived const&)
};

template <typename MatType, int Options, typename Stride>
struct rvalue_from_python_data<Eigen::Ref<MatType, Options, Stride>&>
    : rvalue_from_python_storage<Eigen::Ref<MatType, Options, Stride>&> {
  typedef Eigen::Ref<MatType, Options, Stride> RefType;

#if (!defined(__MWERKS__) || __MWERKS__ >= 0x3000) &&                        \
    (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 245) &&                 \
    (!defined(__DECCXX_VER) || __DECCXX_VER > 60590014) &&                   \
    !defined(BOOST_PYTHON_SYNOPSIS) /* Synopsis' OpenCXX has trouble parsing \
                                       this */
  // This must always be a POD struct with m_data its first member.
  BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<RefType>,
                                            stage1) == 0);
#endif

  // The usual constructor
  rvalue_from_python_data(rvalue_from_python_stage1_data const& _stage1) {
    this->stage1 = _stage1;
  }

  // This constructor just sets m_convertible -- used by
  // implicitly_convertible<> to perform the final step of the
  // conversion, where the construct() function is already known.
  rvalue_from_python_data(void* convertible) {
    this->stage1.convertible = convertible;
  }

  // Destroys any object constructed in the storage.
  ~rvalue_from_python_data() {
    typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
    if (this->stage1.convertible == this->storage.bytes)
      static_cast<StorageType*>((void*)this->storage.bytes)->~StorageType();
  }
};

template <typename MatType, int Options, typename Stride>
struct rvalue_from_python_data<
    const Eigen::Ref<const MatType, Options, Stride>&>
    : rvalue_from_python_storage<
          const Eigen::Ref<const MatType, Options, Stride>&> {
  typedef Eigen::Ref<const MatType, Options, Stride> RefType;

#if (!defined(__MWERKS__) || __MWERKS__ >= 0x3000) &&                        \
    (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 245) &&                 \
    (!defined(__DECCXX_VER) || __DECCXX_VER > 60590014) &&                   \
    !defined(BOOST_PYTHON_SYNOPSIS) /* Synopsis' OpenCXX has trouble parsing \
                                       this */
  // This must always be a POD struct with m_data its first member.
  BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<RefType>,
                                            stage1) == 0);
#endif

  // The usual constructor
  rvalue_from_python_data(rvalue_from_python_stage1_data const& _stage1) {
    this->stage1 = _stage1;
  }

  // This constructor just sets m_convertible -- used by
  // implicitly_convertible<> to perform the final step of the
  // conversion, where the construct() function is already known.
  rvalue_from_python_data(void* convertible) {
    this->stage1.convertible = convertible;
  }

  // Destroys any object constructed in the storage.
  ~rvalue_from_python_data() {
    typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType;
    if (this->stage1.convertible == this->storage.bytes)
      static_cast<StorageType*>((void*)this->storage.bytes)->~StorageType();
  }
};

}  // namespace converter
}  // namespace python
}  // namespace boost

namespace eigenpy {

template <typename MatOrRefType>
void eigen_from_py_construct(
    PyObject* pyObj, bp::converter::rvalue_from_python_stage1_data* memory) {
  PyArrayObject* pyArray = reinterpret_cast<PyArrayObject*>(pyObj);
  assert((PyArray_DIMS(pyArray)[0] < INT_MAX) &&
         (PyArray_DIMS(pyArray)[1] < INT_MAX));

  bp::converter::rvalue_from_python_storage<MatOrRefType>* storage =
      reinterpret_cast<
          bp::converter::rvalue_from_python_storage<MatOrRefType>*>(
          reinterpret_cast<void*>(memory));

  EigenAllocator<MatOrRefType>::allocate(pyArray, storage);

  memory->convertible = storage->storage.bytes;
}

template <typename EigenType,
          typename BaseType = typename get_eigen_base_type<EigenType>::type>
struct eigen_from_py_impl {
  typedef typename EigenType::Scalar Scalar;

  /// \brief Determine if pyObj can be converted into a MatType object
  static void* convertible(PyObject* pyObj);

  /// \brief Allocate memory and copy pyObj in the new storage
  static void construct(PyObject* pyObj,
                        bp::converter::rvalue_from_python_stage1_data* memory);

  static void registration();
};

template <typename MatType>
struct eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType>> {
  typedef typename MatType::Scalar Scalar;

  /// \brief Determine if pyObj can be converted into a MatType object
  static void* convertible(PyObject* pyObj);

  /// \brief Allocate memory and copy pyObj in the new storage
  static void construct(PyObject* pyObj,
                        bp::converter::rvalue_from_python_stage1_data* memory);

  static void registration();
};

template <typename EigenType,
          typename Scalar =
              typename boost::remove_reference<EigenType>::type::Scalar>
struct EigenFromPy : eigen_from_py_impl<EigenType> {};

template <typename MatType>
void* eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType>>::convertible(
    PyObject* pyObj) {
  if (!call_PyArray_Check(reinterpret_cast<PyObject*>(pyObj))) return 0;

  PyArrayObject* pyArray = reinterpret_cast<PyArrayObject*>(pyObj);

  if (!np_type_is_convertible_into_scalar<Scalar>(
          EIGENPY_GET_PY_ARRAY_TYPE(pyArray)))
    return 0;

  if (MatType::IsVectorAtCompileTime) {
    const Eigen::DenseIndex size_at_compile_time =
        MatType::IsRowMajor ? MatType::ColsAtCompileTime
                            : MatType::RowsAtCompileTime;

    switch (PyArray_NDIM(pyArray)) {
      case 0:
        return 0;
      case 1: {
        if (size_at_compile_time != Eigen::Dynamic) {
          // check that the sizes at compile time matche
          if (PyArray_DIMS(pyArray)[0] == size_at_compile_time)
            return pyArray;
          else
            return 0;
        } else  // This is a dynamic MatType
          return pyArray;
      }
      case 2: {
        // Special care of scalar matrix of dimension 1x1.
        if (PyArray_DIMS(pyArray)[0] == 1 && PyArray_DIMS(pyArray)[1] == 1) {
          if (size_at_compile_time != Eigen::Dynamic) {
            if (size_at_compile_time == 1)
              return pyArray;
            else
              return 0;
          } else  // This is a dynamic MatType
            return pyArray;
        }

        if (PyArray_DIMS(pyArray)[0] > 1 && PyArray_DIMS(pyArray)[1] > 1) {
          return 0;
        }

        if (((PyArray_DIMS(pyArray)[0] == 1) &&
             (MatType::ColsAtCompileTime == 1)) ||
            ((PyArray_DIMS(pyArray)[1] == 1) &&
             (MatType::RowsAtCompileTime == 1))) {
          return 0;
        }

        if (size_at_compile_time !=
            Eigen::Dynamic) {  // This is a fixe size vector
          const Eigen::DenseIndex pyArray_size =
              PyArray_DIMS(pyArray)[0] > PyArray_DIMS(pyArray)[1]
                  ? PyArray_DIMS(pyArray)[0]
                  : PyArray_DIMS(pyArray)[1];
          if (size_at_compile_time != pyArray_size) return 0;
        }
        break;
      }
      default:
        return 0;
    }
  } else  // this is a matrix
  {
    if (PyArray_NDIM(pyArray) ==
        1)  // We can always convert a vector into a matrix
    {
      return pyArray;
    }

    if (PyArray_NDIM(pyArray) != 2) {
      return 0;
    }

    if (PyArray_NDIM(pyArray) == 2) {
      const int R = (int)PyArray_DIMS(pyArray)[0];
      const int C = (int)PyArray_DIMS(pyArray)[1];

      if ((MatType::RowsAtCompileTime != R) &&
          (MatType::RowsAtCompileTime != Eigen::Dynamic))
        return 0;
      if ((MatType::ColsAtCompileTime != C) &&
          (MatType::ColsAtCompileTime != Eigen::Dynamic))
        return 0;
    }
  }

#ifdef NPY_1_8_API_VERSION
  if (!(PyArray_FLAGS(pyArray)))
#else
  if (!(PyArray_FLAGS(pyArray) & NPY_ALIGNED))
#endif
  {
    return 0;
  }

  return pyArray;
}

template <typename MatType>
void eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType>>::construct(
    PyObject* pyObj, bp::converter::rvalue_from_python_stage1_data* memory) {
  eigen_from_py_construct<MatType>(pyObj, memory);
}

template <typename MatType>
void eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType>>::registration() {
  bp::converter::registry::push_back(
      reinterpret_cast<void* (*)(_object*)>(&eigen_from_py_impl::convertible),
      &eigen_from_py_impl::construct, bp::type_id<MatType>()
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
                                          ,
      &eigenpy::expected_pytype_for_arg<MatType>::get_pytype
#endif
  );
}

template <typename EigenType,
          typename BaseType = typename get_eigen_base_type<EigenType>::type>
struct eigen_from_py_converter_impl;

template <typename EigenType>
struct EigenFromPyConverter : eigen_from_py_converter_impl<EigenType> {};

template <typename MatType>
struct eigen_from_py_converter_impl<MatType, Eigen::MatrixBase<MatType>> {
  static void registration() {
    EigenFromPy<MatType>::registration();

    // Add conversion to Eigen::MatrixBase<MatType>
    typedef Eigen::MatrixBase<MatType> MatrixBase;
    EigenFromPy<MatrixBase>::registration();

    // Add conversion to Eigen::EigenBase<MatType>
    typedef Eigen::EigenBase<MatType> EigenBase;
    EigenFromPy<EigenBase, typename MatType::Scalar>::registration();

    // Add conversion to Eigen::PlainObjectBase<MatType>
    typedef Eigen::PlainObjectBase<MatType> PlainObjectBase;
    EigenFromPy<PlainObjectBase>::registration();

#if EIGEN_VERSION_AT_LEAST(3, 2, 0)
    // Add conversion to Eigen::Ref<MatType>
    typedef Eigen::Ref<MatType> RefType;
    EigenFromPy<RefType>::registration();

    // Add conversion to Eigen::Ref<MatType>
    typedef const Eigen::Ref<const MatType> ConstRefType;
    EigenFromPy<ConstRefType>::registration();
#endif
  }
};

template <typename MatType>
struct EigenFromPy<Eigen::MatrixBase<MatType>> : EigenFromPy<MatType> {
  typedef EigenFromPy<MatType> EigenFromPyDerived;
  typedef Eigen::MatrixBase<MatType> Base;

  static void registration() {
    bp::converter::registry::push_back(
        reinterpret_cast<void* (*)(_object*)>(&EigenFromPy::convertible),
        &EigenFromPy::construct, bp::type_id<Base>()
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
                                     ,
        &eigenpy::expected_pytype_for_arg<MatType>::get_pytype
#endif
    );
  }
};

template <typename MatType>
struct EigenFromPy<Eigen::EigenBase<MatType>, typename MatType::Scalar>
    : EigenFromPy<MatType> {
  typedef EigenFromPy<MatType> EigenFromPyDerived;
  typedef Eigen::EigenBase<MatType> Base;

  static void registration() {
    bp::converter::registry::push_back(
        reinterpret_cast<void* (*)(_object*)>(&EigenFromPy::convertible),
        &EigenFromPy::construct, bp::type_id<Base>()
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
                                     ,
        &eigenpy::expected_pytype_for_arg<MatType>::get_pytype
#endif
    );
  }
};

template <typename MatType>
struct EigenFromPy<Eigen::PlainObjectBase<MatType>> : EigenFromPy<MatType> {
  typedef EigenFromPy<MatType> EigenFromPyDerived;
  typedef Eigen::PlainObjectBase<MatType> Base;

  static void registration() {
    bp::converter::registry::push_back(
        reinterpret_cast<void* (*)(_object*)>(&EigenFromPy::convertible),
        &EigenFromPy::construct, bp::type_id<Base>()
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
                                     ,
        &eigenpy::expected_pytype_for_arg<MatType>::get_pytype
#endif
    );
  }
};

#if EIGEN_VERSION_AT_LEAST(3, 2, 0)

template <typename MatType, int Options, typename Stride>
struct EigenFromPy<Eigen::Ref<MatType, Options, Stride>> {
  typedef Eigen::Ref<MatType, Options, Stride> RefType;
  typedef typename MatType::Scalar Scalar;

  /// \brief Determine if pyObj can be converted into a MatType object
  static void* convertible(PyObject* pyObj) {
    if (!call_PyArray_Check(pyObj)) return 0;
    PyArrayObject* pyArray = reinterpret_cast<PyArrayObject*>(pyObj);
    if (!PyArray_ISWRITEABLE(pyArray)) return 0;
    return EigenFromPy<MatType>::convertible(pyObj);
  }

  static void registration() {
    bp::converter::registry::push_back(
        reinterpret_cast<void* (*)(_object*)>(&EigenFromPy::convertible),
        &eigen_from_py_construct<RefType>, bp::type_id<RefType>()
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
                                               ,
        &eigenpy::expected_pytype_for_arg<MatType>::get_pytype
#endif
    );
  }
};

template <typename MatType, int Options, typename Stride>
struct EigenFromPy<const Eigen::Ref<const MatType, Options, Stride>> {
  typedef const Eigen::Ref<const MatType, Options, Stride> ConstRefType;
  typedef typename MatType::Scalar Scalar;

  /// \brief Determine if pyObj can be converted into a MatType object
  static void* convertible(PyObject* pyObj) {
    return EigenFromPy<MatType>::convertible(pyObj);
  }

  static void registration() {
    bp::converter::registry::push_back(
        reinterpret_cast<void* (*)(_object*)>(&EigenFromPy::convertible),
        &eigen_from_py_construct<ConstRefType>, bp::type_id<ConstRefType>()
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
                                                    ,
        &eigenpy::expected_pytype_for_arg<MatType>::get_pytype
#endif
    );
  }
};
#endif

}  // namespace eigenpy

#ifdef EIGENPY_WITH_TENSOR_SUPPORT
#include "eigenpy/tensor/eigen-from-python.hpp"
#endif

#include "eigenpy/sparse/eigen-from-python.hpp"

#endif  // __eigenpy_eigen_from_python_hpp__
