#define NPY_NO_DEPRECATED_API NPY_2_1_API_VERSION
#include <numpy/ndarrayobject.h>

#if NPY_API_VERSION < NPY_2_1_API_VERSION
#error "NUMPY less than 2.0 is not compatible with pyradiomics"
#endif

#include <stdlib.h>
#include <Python.h>
#include "cshape.h"

static const char module_docstring[] = "This module links to C-compiled code for efficient calculation of the surface area "
                                 "in the pyRadiomics package. It provides fast calculation using a marching cubes "
                                 "algoritihm, accessed via ""calculate_surfacearea"". Arguments for this function"
                                 "are positional and consist of two numpy arrays, mask and pixelspacing, which must "
                                 "be supplied in this order. Pixelspacing is a 3 element vector containing the pixel"
                                 "spacing in z, y and x dimension, respectively. All non-zero elements in mask are "
                                 "considered to be a part of the segmentation and are included in the algorithm.";
static const char coefficients_docstring[] = "Arguments: Mask, PixelSpacing. Uses a marching cubes algorithm to calculate an "
                                       "approximation to the total surface area, volume and maximum diameters. "
                                       "The isovalue is considered to be situated midway between a voxel that is part "
                                       "of the segmentation and a voxel that is not.";
static const char coefficients2D_docstring[] = "Arguments: Mask, PixelSpacing. Uses an adapted 2D marching cubes algorithm "
                                         "to calculate an approximation to the total perimeter, surface and maximum "
                                         "diameter. The isovalue is considered to be situated midway between a pixel "
                                         "that is part of the segmentation and a pixel that is not.";

static PyObject *cshape_calculate_coefficients(PyObject *self, PyObject *args);
static PyObject *cshape_calculate_coefficients2D(PyObject *self, PyObject *args);

int check_arrays(PyArrayObject *mask_arr, PyArrayObject *spacing_arr, int *size, int *strides, int dimension);

static PyMethodDef module_methods[] = {
  //{"calculate_", cmatrices_, METH_VARARGS, _docstring},
  { "calculate_coefficients", cshape_calculate_coefficients, METH_VARARGS, coefficients_docstring },
  { "calculate_coefficients2D", cshape_calculate_coefficients2D, METH_VARARGS, coefficients2D_docstring },
  { NULL, NULL, 0, NULL }
};


static struct PyModuleDef moduledef = {
  PyModuleDef_HEAD_INIT,
  "_cshape",           /* m_name */
  module_docstring,    /* m_doc */
  -1,                  /* m_size */
  module_methods,      /* m_methods */
  NULL,                /* m_reload */
  NULL,                /* m_traverse */
  NULL,                /* m_clear */
  NULL,                /* m_free */
};

static PyObject *
moduleinit(void)
{
    PyObject * m = PyModule_Create(&moduledef);

  if (m == NULL)
      return NULL;

  return m;
}

  PyMODINIT_FUNC
  PyInit__cshape(void)
  {
    // Initialize numpy functionality
    import_array();

    return moduleinit();
  }

static PyObject *cshape_calculate_coefficients(PyObject *self, PyObject *args)
{
  PyObject *mask_obj, *spacing_obj;
  int size[3];
  int strides[3];

  double SA, Volume;
  double diameters[4];


  // Parse the input tuple
  if (!PyArg_ParseTuple(args, "OO", &mask_obj, &spacing_obj))
    return NULL;

  // Interpret the input as numpy arrays
  PyArrayObject * mask_arr = (PyArrayObject *)PyArray_FROM_OTF(mask_obj, NPY_BYTE, NPY_ARRAY_FORCECAST | NPY_ARRAY_IN_ARRAY);
  PyArrayObject * spacing_arr = (PyArrayObject *)PyArray_FROM_OTF(spacing_obj, NPY_DOUBLE, NPY_ARRAY_FORCECAST | NPY_ARRAY_IN_ARRAY);

  if (check_arrays(mask_arr, spacing_arr, size, strides, 3) > 0) return NULL;

  // Get arrays in Ctype
  char * mask = (char *)PyArray_DATA(mask_arr);
  double *spacing = (double *)PyArray_DATA(spacing_arr);

  //Calculate Surface Area and volume
  if (calculate_coefficients(mask, size, strides, spacing, &SA, &Volume, diameters))
  {
    // An error has occurred
    Py_XDECREF(mask_arr);
    Py_XDECREF(spacing_arr);
    PyErr_SetString(PyExc_RuntimeError, "Calculation of Shape coefficients failed.");
    return NULL;
  }

  // Clean up
  Py_XDECREF(mask_arr);
  Py_XDECREF(spacing_arr);

  PyObject *diameter_obj = Py_BuildValue("ffff", diameters[0], diameters[1], diameters[2], diameters[3]);
  return Py_BuildValue("ffN", SA, Volume, diameter_obj);
}

static PyObject *cshape_calculate_coefficients2D(PyObject *self, PyObject *args)
{
  PyObject *mask_obj, *spacing_obj;

  int size[2];
  int strides[2];

  double perimeter, surface, diameter;

  // Parse the input tuple
  if (!PyArg_ParseTuple(args, "OO", &mask_obj, &spacing_obj))
    return NULL;

  // Interpret the input as numpy arrays
  PyArrayObject *mask_arr = (PyArrayObject *)PyArray_FROM_OTF(mask_obj, NPY_BYTE, NPY_ARRAY_FORCECAST | NPY_ARRAY_IN_ARRAY);
  PyArrayObject *spacing_arr = (PyArrayObject *)PyArray_FROM_OTF(spacing_obj, NPY_DOUBLE, NPY_ARRAY_FORCECAST | NPY_ARRAY_IN_ARRAY);

  if (check_arrays(mask_arr, spacing_arr, size, strides, 2) > 0) return NULL;

  // Get arrays in Ctype
  char * mask = (char *)PyArray_DATA(mask_arr);
  double * spacing = (double *)PyArray_DATA(spacing_arr);

  //Calculate Surface Area and volume
  if (calculate_coefficients2D(mask, size, strides, spacing, &perimeter, &surface, &diameter))
  {
    // An error has occurred
    Py_XDECREF(mask_arr);
    Py_XDECREF(spacing_arr);
    PyErr_SetString(PyExc_RuntimeError, "Calculation of Shape coefficients failed.");
    return NULL;
  }

  // Clean up
  Py_XDECREF(mask_arr);
  Py_XDECREF(spacing_arr);

  return Py_BuildValue("fff", perimeter, surface, diameter);
}

int check_arrays(PyArrayObject *mask_arr, PyArrayObject *spacing_arr, int *size, int *strides, int dimension)
{
  if (mask_arr == NULL || spacing_arr == NULL)
  {
    Py_XDECREF(mask_arr);
    Py_XDECREF(spacing_arr);
    PyErr_SetString(PyExc_RuntimeError, "Error parsing array arguments.");
    return 1;
  }

  if (PyArray_NDIM(mask_arr) != dimension || PyArray_NDIM(spacing_arr) != 1)
  {
    Py_XDECREF(mask_arr);
    Py_XDECREF(spacing_arr);
    PyErr_Format(PyExc_ValueError, "Expected a %iD array for mask, 1D for spacing.", dimension);
    return 2;
  }

  if ( !PyArray_IS_C_CONTIGUOUS(mask_arr) || !PyArray_IS_C_CONTIGUOUS(spacing_arr))
  {
    Py_XDECREF(mask_arr);
    Py_XDECREF(spacing_arr);
    PyErr_SetString(PyExc_ValueError, "Expecting input arrays to be C-contiguous.");
    return 3;
  }

  if (PyArray_DIM(spacing_arr, 0) != PyArray_NDIM(mask_arr))
  {
    Py_XDECREF(mask_arr);
    Py_XDECREF(spacing_arr);
    PyErr_SetString(PyExc_ValueError, "Expecting spacing array to have shape (3,).");
    return 4;
  }

  // Get sizes and strides of the arrays
  for (int i = 0; i < dimension; i++)
  {
    size[i] = (int)PyArray_DIM(mask_arr, i);
    strides[i] = (int)(PyArray_STRIDE(mask_arr, i) / PyArray_ITEMSIZE(mask_arr));
  }

  return 0;
}
