/* SPDX-FileCopyrightText: 2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup pymathutils
 */

#include <Python.h>

#include "mathutils.hh"

#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_utildefines.h"

#include "../generic/py_capi_utils.hh"

#ifndef MATH_STANDALONE
#  include "BLI_dynstr.h"
#endif

PyDoc_STRVAR(
    /* Wrap. */
    M_Mathutils_doc,
    "This module provides access to math operations.\n"
    "\n"
    ".. note::\n"
    "\n"
    "   Classes, methods and attributes that accept vectors also accept other numeric sequences,\n"
    "   such as tuples, lists.\n"
    "\n"
    "The :mod:`mathutils` module provides the following classes:\n"
    "\n"
    "- :class:`Color`,\n"
    "- :class:`Euler`,\n"
    "- :class:`Matrix`,\n"
    "- :class:`Quaternion`,\n"
    "- :class:`Vector`,\n");
static int mathutils_array_parse_fast(float *array,
                                      int size,
                                      PyObject *value_fast,
                                      const char *error_prefix)
{
  PyObject *item;
  PyObject **value_fast_items = PySequence_Fast_ITEMS(value_fast);

  int i;

  i = size;
  do {
    i--;
    if (((array[i] = PyFloat_AsDouble(item = value_fast_items[i])) == -1.0f) && PyErr_Occurred()) {
      PyErr_Format(PyExc_TypeError,
                   "%.200s: sequence index %d expected a number, "
                   "found '%.200s' type, ",
                   error_prefix,
                   i,
                   Py_TYPE(item)->tp_name);
      size = -1;
      break;
    }
  } while (i);

  return size;
}

Py_hash_t mathutils_array_hash(const float *array, size_t array_len)
{
  int i;
  Py_uhash_t x; /* Unsigned for defined overflow behavior. */
  Py_hash_t y;
  Py_uhash_t mult;
  Py_ssize_t len;

  mult = _PyHASH_MULTIPLIER;
  len = array_len;
  x = 0x345678UL;
  i = 0;
  while (--len >= 0) {
    y = _Py_HashDouble(nullptr, double(array[i++]));
    if (y == -1) {
      return -1;
    }
    x = (x ^ y) * mult;
    /* the cast might truncate len; that doesn't change hash stability */
    mult += (Py_hash_t)(82520UL + len + len);
  }
  x += 97531UL;
  if (x == (Py_uhash_t)-1) {
    x = -2;
  }
  return x;
}

int mathutils_array_parse(
    float *array, int array_num_min, int array_num_max, PyObject *value, const char *error_prefix)
{
  const uint flag = array_num_max;
  int num;

  array_num_max &= ~MU_ARRAY_FLAGS;

#if 1 /* approx 6x speedup for mathutils types */

  if ((num = VectorObject_Check(value) ? ((VectorObject *)value)->vec_num : 0) ||
      (num = EulerObject_Check(value) ? 3 : 0) || (num = QuaternionObject_Check(value) ? 4 : 0) ||
      (num = ColorObject_Check(value) ? 3 : 0))
  {
    if (BaseMath_ReadCallback((BaseMathObject *)value) == -1) {
      return -1;
    }

    if (flag & MU_ARRAY_SPILL) {
      CLAMP_MAX(num, array_num_max);
    }

    if (num > array_num_max || num < array_num_min) {
      if (array_num_max == array_num_min) {
        PyErr_Format(PyExc_ValueError,
                     "%.200s: sequence length is %d, expected %d",
                     error_prefix,
                     num,
                     array_num_max);
      }
      else {
        PyErr_Format(PyExc_ValueError,
                     "%.200s: sequence length is %d, expected [%d - %d]",
                     error_prefix,
                     num,
                     array_num_min,
                     array_num_max);
      }
      return -1;
    }

    memcpy(array, ((const BaseMathObject *)value)->data, num * sizeof(float));
  }
  else
#endif
  {
    PyObject *value_fast = nullptr;

    /* non list/tuple cases */
    if (!(value_fast = PySequence_Fast(value, error_prefix))) {
      /* PySequence_Fast sets the error */
      return -1;
    }

    num = PySequence_Fast_GET_SIZE(value_fast);

    if (flag & MU_ARRAY_SPILL) {
      CLAMP_MAX(num, array_num_max);
    }

    if (num > array_num_max || num < array_num_min) {
      if (array_num_max == array_num_min) {
        PyErr_Format(PyExc_ValueError,
                     "%.200s: sequence length is %d, expected %d",
                     error_prefix,
                     num,
                     array_num_max);
      }
      else {
        PyErr_Format(PyExc_ValueError,
                     "%.200s: sequence length is %d, expected [%d - %d]",
                     error_prefix,
                     num,
                     array_num_min,
                     array_num_max);
      }
      Py_DECREF(value_fast);
      return -1;
    }

    num = mathutils_array_parse_fast(array, num, value_fast, error_prefix);
    Py_DECREF(value_fast);
  }

  if (num != -1) {
    if (flag & MU_ARRAY_ZERO) {
      const int array_num_left = array_num_max - num;
      if (array_num_left) {
        memset(&array[num], 0, sizeof(float) * array_num_left);
      }
    }
  }

  return num;
}

int mathutils_array_parse_alloc(float **array,
                                int array_num_min,
                                PyObject *value,
                                const char *error_prefix)
{
  int num;

#if 1 /* approx 6x speedup for mathutils types */

  if ((num = VectorObject_Check(value) ? ((VectorObject *)value)->vec_num : 0) ||
      (num = EulerObject_Check(value) ? 3 : 0) || (num = QuaternionObject_Check(value) ? 4 : 0) ||
      (num = ColorObject_Check(value) ? 3 : 0))
  {
    if (BaseMath_ReadCallback((BaseMathObject *)value) == -1) {
      return -1;
    }

    if (num < array_num_min) {
      PyErr_Format(PyExc_ValueError,
                   "%.200s: sequence size is %d, expected >= %d",
                   error_prefix,
                   num,
                   array_num_min);
      return -1;
    }

    *array = static_cast<float *>(PyMem_Malloc(num * sizeof(float)));
    memcpy(*array, ((const BaseMathObject *)value)->data, num * sizeof(float));
    return num;
  }

#endif

  PyObject *value_fast = nullptr;
  // *array = nullptr;
  int ret;

  /* non list/tuple cases */
  if (!(value_fast = PySequence_Fast(value, error_prefix))) {
    /* PySequence_Fast sets the error */
    return -1;
  }

  num = PySequence_Fast_GET_SIZE(value_fast);

  if (num < array_num_min) {
    Py_DECREF(value_fast);
    PyErr_Format(PyExc_ValueError,
                 "%.200s: sequence size is %d, expected >= %d",
                 error_prefix,
                 num,
                 array_num_min);
    return -1;
  }

  *array = static_cast<float *>(PyMem_Malloc(num * sizeof(float)));

  ret = mathutils_array_parse_fast(*array, num, value_fast, error_prefix);
  Py_DECREF(value_fast);

  if (ret == -1) {
    PyMem_Free(*array);
  }

  return ret;
}

int mathutils_array_parse_alloc_v(float **array,
                                  int array_dim,
                                  PyObject *value,
                                  const char *error_prefix)
{
  PyObject *value_fast;
  const int array_dim_flag = array_dim;
  int i, num;

  /* non list/tuple cases */
  if (!(value_fast = PySequence_Fast(value, error_prefix))) {
    /* PySequence_Fast sets the error */
    return -1;
  }

  num = PySequence_Fast_GET_SIZE(value_fast);

  if (num != 0) {
    PyObject **value_fast_items = PySequence_Fast_ITEMS(value_fast);
    float *fp;

    array_dim &= ~MU_ARRAY_FLAGS;

    fp = *array = static_cast<float *>(PyMem_Malloc(num * array_dim * sizeof(float)));

    for (i = 0; i < num; i++, fp += array_dim) {
      PyObject *item = value_fast_items[i];

      if (mathutils_array_parse(fp, array_dim, array_dim_flag, item, error_prefix) == -1) {
        PyMem_Free(*array);
        *array = nullptr;
        num = -1;
        break;
      }
    }
  }

  Py_DECREF(value_fast);
  return num;
}

int mathutils_int_array_parse(int *array, int array_dim, PyObject *value, const char *error_prefix)
{
  int size, i;
  PyObject *value_fast, **value_fast_items, *item;

  if (!(value_fast = PySequence_Fast(value, error_prefix))) {
    /* PySequence_Fast sets the error */
    return -1;
  }

  if ((size = PySequence_Fast_GET_SIZE(value_fast)) != array_dim) {
    PyErr_Format(PyExc_ValueError,
                 "%.200s: sequence size is %d, expected %d",
                 error_prefix,
                 size,
                 array_dim);
    Py_DECREF(value_fast);
    return -1;
  }

  value_fast_items = PySequence_Fast_ITEMS(value_fast);
  i = size;
  while (i > 0) {
    i--;
    if (((array[i] = PyC_Long_AsI32(item = value_fast_items[i])) == -1) && PyErr_Occurred()) {
      PyErr_Format(PyExc_TypeError, "%.200s: sequence index %d expected an int", error_prefix, i);
      size = -1;
      break;
    }
  }
  Py_DECREF(value_fast);

  return size;
}

int mathutils_array_parse_alloc_vi(int **array,
                                   int array_dim,
                                   PyObject *value,
                                   const char *error_prefix)
{
  PyObject *value_fast;
  int i, size;

  if (!(value_fast = PySequence_Fast(value, error_prefix))) {
    /* PySequence_Fast sets the error */
    return -1;
  }

  size = PySequence_Fast_GET_SIZE(value_fast);

  if (size != 0) {
    PyObject **value_fast_items = PySequence_Fast_ITEMS(value_fast);
    int *ip;

    ip = *array = static_cast<int *>(PyMem_Malloc(size * array_dim * sizeof(int)));

    for (i = 0; i < size; i++, ip += array_dim) {
      PyObject *item = value_fast_items[i];

      if (mathutils_int_array_parse(ip, array_dim, item, error_prefix) == -1) {
        PyMem_Free(*array);
        *array = nullptr;
        size = -1;
        break;
      }
    }
  }

  Py_DECREF(value_fast);
  return size;
}

bool mathutils_array_parse_alloc_viseq(PyObject *value,
                                       const char *error_prefix,
                                       blender::Array<blender::Vector<int>> &r_data)
{
  PyObject *value_fast;
  if (!(value_fast = PySequence_Fast(value, error_prefix))) {
    /* PySequence_Fast sets the error */
    return false;
  }

  const int size = PySequence_Fast_GET_SIZE(value_fast);
  if (size != 0) {
    PyObject **value_fast_items = PySequence_Fast_ITEMS(value_fast);
    r_data.reinitialize(size);
    for (const int64_t i : r_data.index_range()) {
      PyObject *subseq = value_fast_items[i];
      const int subseq_len = int(PySequence_Size(subseq));
      if (subseq_len == -1) {
        PyErr_Format(
            PyExc_ValueError, "%.200s: sequence expected to have subsequences", error_prefix);
        Py_DECREF(value_fast);
        return false;
      }
      r_data[i].resize(subseq_len);
      blender::MutableSpan<int> group = r_data[i];
      if (mathutils_int_array_parse(group.data(), group.size(), subseq, error_prefix) == -1) {
        Py_DECREF(value_fast);
        return false;
      }
    }
  }

  Py_DECREF(value_fast);
  return true;
}

int mathutils_any_to_rotmat(float rmat[3][3], PyObject *value, const char *error_prefix)
{
  if (EulerObject_Check(value)) {
    if (BaseMath_ReadCallback((BaseMathObject *)value) == -1) {
      return -1;
    }

    eulO_to_mat3(rmat, ((const EulerObject *)value)->eul, ((const EulerObject *)value)->order);
    return 0;
  }
  if (QuaternionObject_Check(value)) {
    if (BaseMath_ReadCallback((BaseMathObject *)value) == -1) {
      return -1;
    }

    float tquat[4];
    normalize_qt_qt(tquat, ((const QuaternionObject *)value)->quat);
    quat_to_mat3(rmat, tquat);
    return 0;
  }
  if (MatrixObject_Check(value)) {
    if (BaseMath_ReadCallback((BaseMathObject *)value) == -1) {
      return -1;
    }
    if (((MatrixObject *)value)->row_num < 3 || ((MatrixObject *)value)->col_num < 3) {
      PyErr_Format(
          PyExc_ValueError, "%.200s: matrix must have minimum 3x3 dimensions", error_prefix);
      return -1;
    }

    matrix_as_3x3(rmat, (MatrixObject *)value);
    normalize_m3(rmat);
    return 0;
  }

  PyErr_Format(PyExc_TypeError,
               "%.200s: expected a Euler, Quaternion or Matrix type, "
               "found %.200s",
               error_prefix,
               Py_TYPE(value)->tp_name);
  return -1;
}

/* ----------------------------------MATRIX FUNCTIONS-------------------- */

/* Utility functions */

/* LomontRRDCompare4, Ever Faster Float Comparisons by Randy Dillon */
/* XXX We may want to use 'safer' BLI's compare_ff_relative ultimately?
 * LomontRRDCompare4() is an optimized version of Dawson's AlmostEqual2sComplement()
 * (see [1] and [2]).
 * Dawson himself now claims this is not a 'safe' thing to do
 * (pushing ULP method beyond its limits),
 * an recommends using work from [3] instead, which is done in BLI func...
 *
 * [1] http://www.randydillon.org/Papers/2007/everfast.htm
 * [2] http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
 * [3] https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
 * instead.
 */
#define SIGNMASK(i) (-int((uint(i)) >> 31))

int EXPP_FloatsAreEqual(float af, float bf, int maxDiff)
{
  /* solid, fast routine across all platforms
   * with constant time behavior */
  const int ai = *(const int *)(&af);
  const int bi = *(const int *)(&bf);
  const int test = SIGNMASK(ai ^ bi);
  int diff, v1, v2;

  BLI_assert((0 == test) || (0xFFFFFFFF == test));
  diff = (ai ^ (test & 0x7fffffff)) - bi;
  v1 = maxDiff + diff;
  v2 = maxDiff - diff;
  return (v1 | v2) >= 0;
}

/*---------------------- EXPP_VectorsAreEqual -------------------------
 * Builds on EXPP_FloatsAreEqual to test vectors */

int EXPP_VectorsAreEqual(const float *vecA, const float *vecB, int size, int floatSteps)
{
  int x;
  for (x = 0; x < size; x++) {
    if (EXPP_FloatsAreEqual(vecA[x], vecB[x], floatSteps) == 0) {
      return 0;
    }
  }
  return 1;
}

#ifndef MATH_STANDALONE
PyObject *mathutils_dynstr_to_py(DynStr *ds)
{
  const int ds_len = BLI_dynstr_get_len(ds); /* space for \0 */
  char *ds_buf = static_cast<char *>(PyMem_Malloc(ds_len + 1));
  PyObject *ret;
  BLI_dynstr_get_cstring_ex(ds, ds_buf);
  BLI_dynstr_free(ds);
  ret = PyUnicode_FromStringAndSize(ds_buf, ds_len);
  PyMem_Free(ds_buf);
  return ret;
}
#endif

/* Mathutils Callbacks */

/* For mathutils internal use only,
 * eventually should re-alloc but to start with we only have a few users. */
#define MATHUTILS_TOT_CB 17
static Mathutils_Callback *mathutils_callbacks[MATHUTILS_TOT_CB] = {nullptr};

uchar Mathutils_RegisterCallback(Mathutils_Callback *cb)
{
  uchar i;

  /* find the first free slot */
  for (i = 0; mathutils_callbacks[i]; i++) {
    if (mathutils_callbacks[i] == cb) {
      /* already registered? */
      return i;
    }
  }

  BLI_assert(i + 1 < MATHUTILS_TOT_CB);

  mathutils_callbacks[i] = cb;
  return i;
}

int _BaseMathObject_CheckCallback(BaseMathObject *self)
{
  Mathutils_Callback *cb = mathutils_callbacks[self->cb_type];
  if (LIKELY(cb->check(self) != -1)) {
    return 0;
  }
  return -1;
}

int _BaseMathObject_ReadCallback(BaseMathObject *self)
{
  /* NOTE: use macros to check for nullptr. */

  Mathutils_Callback *cb = mathutils_callbacks[self->cb_type];
  if (LIKELY(cb->get(self, self->cb_subtype) != -1)) {
    return 0;
  }

  if (!PyErr_Occurred()) {
    PyErr_Format(PyExc_RuntimeError, "%s read, user has become invalid", Py_TYPE(self)->tp_name);
  }
  return -1;
}

int _BaseMathObject_WriteCallback(BaseMathObject *self)
{
  Mathutils_Callback *cb = mathutils_callbacks[self->cb_type];
  if (LIKELY(cb->set(self, self->cb_subtype) != -1)) {
    return 0;
  }

  if (!PyErr_Occurred()) {
    PyErr_Format(PyExc_RuntimeError, "%s write, user has become invalid", Py_TYPE(self)->tp_name);
  }
  return -1;
}

int _BaseMathObject_ReadIndexCallback(BaseMathObject *self, int index)
{
  Mathutils_Callback *cb = mathutils_callbacks[self->cb_type];
  if (LIKELY(cb->get_index(self, self->cb_subtype, index) != -1)) {
    return 0;
  }

  if (!PyErr_Occurred()) {
    PyErr_Format(
        PyExc_RuntimeError, "%s read index, user has become invalid", Py_TYPE(self)->tp_name);
  }
  return -1;
}

int _BaseMathObject_WriteIndexCallback(BaseMathObject *self, int index)
{
  Mathutils_Callback *cb = mathutils_callbacks[self->cb_type];
  if (LIKELY(cb->set_index(self, self->cb_subtype, index) != -1)) {
    return 0;
  }

  if (!PyErr_Occurred()) {
    PyErr_Format(
        PyExc_RuntimeError, "%s write index, user has become invalid", Py_TYPE(self)->tp_name);
  }
  return -1;
}

void _BaseMathObject_RaiseFrozenExc(const BaseMathObject *self)
{
  PyErr_Format(PyExc_TypeError, "%s is frozen (immutable)", Py_TYPE(self)->tp_name);
}

void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self)
{
  PyErr_Format(
      PyExc_TypeError, "%s is not frozen (mutable), call freeze first", Py_TYPE(self)->tp_name);
}

int _BaseMathObject_ResizeOkOrRaiseExc(BaseMathObject *self, const char *error_prefix)
{
  if (UNLIKELY(self->flag & BASE_MATH_FLAG_IS_FROZEN)) {
    PyErr_Format(PyExc_ValueError, "%s: cannot resize frozen data", error_prefix);
    return -1;
  }
  if (UNLIKELY(self->flag & BASE_MATH_FLAG_IS_WRAP)) {
    PyErr_Format(PyExc_ValueError, "%s: cannot resize wrapped data", error_prefix);
    return -1;
  }
  if (UNLIKELY(self->flag & BASE_MATH_FLAG_HAS_BUFFER_VIEW)) {
    PyErr_Format(PyExc_BufferError,
                 "%s: cannot resize data while exported to buffer protocol",
                 error_prefix);
    return -1;
  }
  if (UNLIKELY(self->cb_user)) {
    PyErr_Format(PyExc_ValueError, "%s: cannot resize owned data", error_prefix);
    return -1;
  }
  return 0;
}

int _BaseMathObject_RaiseBufferViewExc(BaseMathObject *self, Py_buffer *view, int flags)
{
  if (UNLIKELY(view == nullptr)) {
    PyErr_SetString(PyExc_BufferError, "null view in get-buffer is obsolete");
    return -1;
  }
  if (UNLIKELY(self->flag & BASE_MATH_FLAG_HAS_BUFFER_VIEW)) {
    PyErr_SetString(PyExc_BufferError,
                    "Data is already exported via buffer protocol, "
                    "multiple simultaneous exports are not allowed.");
    return -1;
  }
  if (flags & PyBUF_WRITABLE) {
    if (UNLIKELY(BaseMath_WriteCallback(self) == -1)) {
      return -1;
    }
    if (UNLIKELY(self->flag & BASE_MATH_FLAG_IS_FROZEN)) {
      PyErr_SetString(PyExc_BufferError, "Data is frozen, cannot get a writable buffer");
      return -1;
    }
  }
  return 0;
}

/* #BaseMathObject generic functions for all mathutils types. */

char BaseMathObject_owner_doc[] = "The item this is wrapping or None  (read-only).";
PyObject *BaseMathObject_owner_get(BaseMathObject *self, void * /*closure*/)
{
  PyObject *ret = self->cb_user ? self->cb_user : Py_None;
  return Py_NewRef(ret);
}

char BaseMathObject_is_wrapped_doc[] =
    "True when this object wraps external data (read-only).\n\n:type: bool";
PyObject *BaseMathObject_is_wrapped_get(BaseMathObject *self, void * /*closure*/)
{
  return PyBool_FromLong((self->flag & BASE_MATH_FLAG_IS_WRAP) != 0);
}

char BaseMathObject_is_frozen_doc[] =
    "True when this object has been frozen (read-only).\n\n:type: bool";
PyObject *BaseMathObject_is_frozen_get(BaseMathObject *self, void * /*closure*/)
{
  return PyBool_FromLong((self->flag & BASE_MATH_FLAG_IS_FROZEN) != 0);
}

char BaseMathObject_is_valid_doc[] = "True when the owner of this data is valid.\n\n:type: bool";
PyObject *BaseMathObject_is_valid_get(BaseMathObject *self, void * /*closure*/)
{
  return PyBool_FromLong(BaseMath_CheckCallback(self) == 0);
}

char BaseMathObject_freeze_doc[] =
    ".. function:: freeze()\n"
    "\n"
    "   Make this object immutable.\n"
    "\n"
    "   After this the object can be hashed, used in dictionaries & sets.\n"
    "\n"
    "   :return: An instance of this object.\n";
PyObject *BaseMathObject_freeze(BaseMathObject *self)
{
  if ((self->flag & BASE_MATH_FLAG_IS_WRAP) || (self->cb_user != nullptr)) {
    PyErr_SetString(PyExc_TypeError, "Cannot freeze wrapped/owned data");
    return nullptr;
  }

  if (self->flag & BASE_MATH_FLAG_HAS_BUFFER_VIEW) {
    PyErr_SetString(PyExc_BufferError, "Cannot freeze data while exported to buffer protocol");
    return nullptr;
  }

  self->flag |= BASE_MATH_FLAG_IS_FROZEN;

  return Py_NewRef(self);
}

int BaseMathObject_traverse(BaseMathObject *self, visitproc visit, void *arg)
{
  Py_VISIT(self->cb_user);
  return 0;
}

int BaseMathObject_clear(BaseMathObject *self)
{
  Py_CLEAR(self->cb_user);
  return 0;
}

/** Only to validate assumptions when debugging. */
#ifndef NDEBUG
static bool BaseMathObject_is_tracked(BaseMathObject *self)
{
  PyObject *cb_user = self->cb_user;
  self->cb_user = (PyObject *)uintptr_t(-1);
  bool is_tracked = PyObject_GC_IsTracked((PyObject *)self);
  self->cb_user = cb_user;
  return is_tracked;
}
#endif /* !NDEBUG */

void BaseMathObject_dealloc(BaseMathObject *self)
{
  /* only free non wrapped */
  if ((self->flag & BASE_MATH_FLAG_IS_WRAP) == 0) {
    PyMem_Free(self->data);
  }

  if (self->cb_user) {
    BLI_assert(BaseMathObject_is_tracked(self) == true);
    PyObject_GC_UnTrack(self);
    BaseMathObject_clear(self);
  }
  else if (!BaseMathObject_CheckExact(self)) {
    /* Sub-classed types get an extra track (in Pythons internal `subtype_dealloc` function). */
    BLI_assert(BaseMathObject_is_tracked(self) == true);
    PyObject_GC_UnTrack(self);
    BLI_assert(BaseMathObject_is_tracked(self) == false);
  }

  Py_TYPE(self)->tp_free(self);  // PyObject_DEL(self); /* breaks sub-types. */
}

int BaseMathObject_is_gc(BaseMathObject *self)
{
  return self->cb_user != nullptr;
}

PyObject *_BaseMathObject_new_impl(PyTypeObject *root_type, PyTypeObject *base_type)
{
  PyObject *obj;
  if (ELEM(base_type, nullptr, root_type)) {
    obj = _PyObject_GC_New(root_type);
    if (obj) {
      BLI_assert(BaseMathObject_is_tracked((BaseMathObject *)obj) == false);
    }
  }
  else {
    /* Calls Generic allocation function which always tracks
     * (because `root_type` is flagged for GC). */
    obj = base_type->tp_alloc(base_type, 0);
    if (obj) {
      BLI_assert(BaseMathObject_is_tracked((BaseMathObject *)obj) == true);
      PyObject_GC_UnTrack(obj);
      BLI_assert(BaseMathObject_is_tracked((BaseMathObject *)obj) == false);
    }
  }

  return obj;
}

/*----------------------------MODULE INIT-------------------------*/
static PyMethodDef M_Mathutils_methods[] = {
    {nullptr, nullptr, 0, nullptr},
};

static PyModuleDef M_Mathutils_module_def = {
    /*m_base*/ PyModuleDef_HEAD_INIT,
    /*m_name*/ "mathutils",
    /*m_doc*/ M_Mathutils_doc,
    /*m_size*/ 0,
    /*m_methods*/ M_Mathutils_methods,
    /*m_slots*/ nullptr,
    /*m_traverse*/ nullptr,
    /*m_clear*/ nullptr,
    /*m_free*/ nullptr,
};

/* submodules only */
#include "mathutils_geometry.hh"
#include "mathutils_interpolate.hh"
#ifndef MATH_STANDALONE
#  include "mathutils_bvhtree.hh"
#  include "mathutils_kdtree.hh"
#  include "mathutils_noise.hh"
#endif

PyMODINIT_FUNC PyInit_mathutils()
{
  PyObject *mod;
  PyObject *submodule;
  PyObject *sys_modules = PyImport_GetModuleDict();

  if (PyType_Ready(&vector_Type) < 0) {
    return nullptr;
  }
  if (PyType_Ready(&matrix_Type) < 0) {
    return nullptr;
  }
  if (PyType_Ready(&matrix_access_Type) < 0) {
    return nullptr;
  }
  if (PyType_Ready(&euler_Type) < 0) {
    return nullptr;
  }
  if (PyType_Ready(&quaternion_Type) < 0) {
    return nullptr;
  }
  if (PyType_Ready(&color_Type) < 0) {
    return nullptr;
  }

  mod = PyModule_Create(&M_Mathutils_module_def);

  /* each type has its own new() function */
  PyModule_AddType(mod, &vector_Type);
  PyModule_AddType(mod, &matrix_Type);
  PyModule_AddType(mod, &euler_Type);
  PyModule_AddType(mod, &quaternion_Type);
  PyModule_AddType(mod, &color_Type);

  /* submodule */
  PyModule_AddObject(mod, "geometry", (submodule = PyInit_mathutils_geometry()));
  /* XXX, python doesn't do imports with this usefully yet
   * 'from mathutils.geometry import PolyFill'
   * ...fails without this. */
  PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule);

  PyModule_AddObject(mod, "interpolate", (submodule = PyInit_mathutils_interpolate()));
  /* XXX, python doesn't do imports with this usefully yet
   * 'from mathutils.geometry import PolyFill'
   * ...fails without this. */
  PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule);

#ifndef MATH_STANDALONE
  /* Noise submodule */
  PyModule_AddObject(mod, "noise", (submodule = PyInit_mathutils_noise()));
  PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule);

  /* BVHTree submodule */
  PyModule_AddObject(mod, "bvhtree", (submodule = PyInit_mathutils_bvhtree()));
  PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule);

  /* KDTree_3d submodule */
  PyModule_AddObject(mod, "kdtree", (submodule = PyInit_mathutils_kdtree()));
  PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule);
#endif

  mathutils_matrix_row_cb_index = Mathutils_RegisterCallback(&mathutils_matrix_row_cb);
  mathutils_matrix_col_cb_index = Mathutils_RegisterCallback(&mathutils_matrix_col_cb);
  mathutils_matrix_translation_cb_index = Mathutils_RegisterCallback(
      &mathutils_matrix_translation_cb);

  return mod;
}
