/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/** \file
 * \ingroup pythonintern
 *
 * This file deals with array access for 'BPy_PropertyArrayRNA' from bpy_rna.c
 */

#include <Python.h>

#include "CLG_log.h"

#include "BLI_utildefines.h"

#include "RNA_types.h"

#include "bpy_rna.h"

#include "MEM_guardedalloc.h"

#include "RNA_access.h"

#include "BPY_extern_clog.h"

#include "../generic/py_capi_utils.h"

#define USE_MATHUTILS

#ifdef USE_MATHUTILS
#  include "../mathutils/mathutils.h" /* so we can have mathutils callbacks */
#endif

#define MAX_ARRAY_DIMENSION 10

struct ItemConvertArgData;

typedef void (*ItemConvertFunc)(const struct ItemConvertArgData *arg, PyObject *, char *);
typedef int (*ItemTypeCheckFunc)(PyObject *);
typedef void (*RNA_SetArrayFunc)(PointerRNA *, PropertyRNA *, const char *);
typedef void (*RNA_SetIndexFunc)(PointerRNA *, PropertyRNA *, int index, void *);

struct ItemConvertArgData {
  union {
    struct {
      int range[2];
    } int_data;
    struct {
      float range[2];
    } float_data;
  };
};

/**
 * Callback and args needed to apply the value (clamp range for now)
 */
typedef struct ItemConvert_FuncArg {
  ItemConvertFunc func;
  struct ItemConvertArgData arg;
} ItemConvert_FuncArg;

/*
 * arr[3][4][5]
 *     0  1  2  <- dimension index
 */

/*
 *  arr[2] = x
 *
 *  py_to_array_index(arraydim=0, arrayoffset=0, index=2)
 *      validate_array(lvalue_dim=0)
 *      ... make real index ...
 */

/* arr[3] = x, self->arraydim is 0, lvalue_dim is 1 */
/* Ensures that a python sequence has expected number of
 * items/sub-items and items are of desired type. */
static int validate_array_type(PyObject *seq,
                               int dim,
                               int totdim,
                               int dimsize[],
                               const bool is_dynamic,
                               ItemTypeCheckFunc check_item_type,
                               const char *item_type_str,
                               const char *error_prefix)
{
  Py_ssize_t i;

  /* not the last dimension */
  if (dim + 1 < totdim) {
    /* check that a sequence contains dimsize[dim] items */
    const int seq_size = PySequence_Size(seq);
    if (seq_size == -1) {
      PyErr_Format(PyExc_ValueError,
                   "%s sequence expected at dimension %d, not '%s'",
                   error_prefix,
                   dim + 1,
                   Py_TYPE(seq)->tp_name);
      return -1;
    }
    for (i = 0; i < seq_size; i++) {
      Py_ssize_t item_seq_size;
      PyObject *item;
      bool ok = true;
      item = PySequence_GetItem(seq, i);

      if (item == NULL) {
        PyErr_Format(PyExc_TypeError,
                     "%s sequence type '%s' failed to retrieve index %d",
                     error_prefix,
                     Py_TYPE(seq)->tp_name,
                     i);
        ok = 0;
      }
      else if ((item_seq_size = PySequence_Size(item)) == -1) {
        /* BLI_snprintf(error_str, error_str_size, "expected a sequence of %s", item_type_str); */
        PyErr_Format(PyExc_TypeError,
                     "%s expected a sequence of %s, not %s",
                     error_prefix,
                     item_type_str,
                     Py_TYPE(item)->tp_name);
        ok = 0;
      }
      /* arr[3][4][5]
       * dimsize[1] = 4
       * dimsize[2] = 5
       *
       * dim = 0 */
      else if (item_seq_size != dimsize[dim + 1]) {
        /* BLI_snprintf(error_str, error_str_size,
         *              "sequences of dimension %d should contain %d items",
         *              dim + 1, dimsize[dim + 1]); */
        PyErr_Format(PyExc_ValueError,
                     "%s sequences of dimension %d should contain %d items, not %d",
                     error_prefix,
                     dim + 1,
                     dimsize[dim + 1],
                     item_seq_size);
        ok = 0;
      }
      else if (validate_array_type(item,
                                   dim + 1,
                                   totdim,
                                   dimsize,
                                   is_dynamic,
                                   check_item_type,
                                   item_type_str,
                                   error_prefix) == -1) {
        ok = 0;
      }

      Py_XDECREF(item);

      if (!ok) {
        return -1;
      }
    }
  }
  else {
    /* check that items are of correct type */
    const int seq_size = PySequence_Size(seq);
    if (seq_size == -1) {
      PyErr_Format(PyExc_ValueError,
                   "%s sequence expected at dimension %d, not '%s'",
                   error_prefix,
                   dim + 1,
                   Py_TYPE(seq)->tp_name);
      return -1;
    }
    else if ((seq_size != dimsize[dim]) && (is_dynamic == false)) {
      PyErr_Format(PyExc_ValueError,
                   "%s sequences of dimension %d should contain %d items, not %d",
                   error_prefix,
                   dim,
                   dimsize[dim],
                   seq_size);
      return -1;
    }

    for (i = 0; i < seq_size; i++) {
      PyObject *item = PySequence_GetItem(seq, i);

      if (item == NULL) {
        PyErr_Format(PyExc_TypeError,
                     "%s sequence type '%s' failed to retrieve index %d",
                     error_prefix,
                     Py_TYPE(seq)->tp_name,
                     i);
        return -1;
      }
      else if (!check_item_type(item)) {
        Py_DECREF(item);

#if 0
        BLI_snprintf(
            error_str, error_str_size, "sequence items should be of type %s", item_type_str);
#endif
        PyErr_Format(PyExc_TypeError,
                     "%s expected sequence items of type %s, not %s",
                     error_prefix,
                     item_type_str,
                     Py_TYPE(item)->tp_name);
        return -1;
      }

      Py_DECREF(item);
    }
  }

  return 0; /* ok */
}

/* Returns the number of items in a single- or multi-dimensional sequence. */
static int count_items(PyObject *seq, int dim)
{
  int totitem = 0;

  if (dim > 1) {
    const Py_ssize_t seq_size = PySequence_Size(seq);
    Py_ssize_t i;
    for (i = 0; i < seq_size; i++) {
      PyObject *item = PySequence_GetItem(seq, i);
      if (item) {
        const int tot = count_items(item, dim - 1);
        Py_DECREF(item);
        if (tot != -1) {
          totitem += tot;
        }
        else {
          totitem = -1;
          break;
        }
      }
      else {
        totitem = -1;
        break;
      }
    }
  }
  else {
    totitem = PySequence_Size(seq);
  }

  return totitem;
}

/* Modifies property array length if needed and PROP_DYNAMIC flag is set. */
static int validate_array_length(PyObject *rvalue,
                                 PointerRNA *ptr,
                                 PropertyRNA *prop,
                                 int lvalue_dim,
                                 int *r_totitem,
                                 const char *error_prefix)
{
  int dimsize[MAX_ARRAY_DIMENSION];
  int tot, totdim, len;

  totdim = RNA_property_array_dimension(ptr, prop, dimsize);
  tot = count_items(rvalue, totdim - lvalue_dim);

  if (tot == -1) {
    PyErr_Format(PyExc_ValueError,
                 "%s %.200s.%.200s, error validating the sequence length",
                 error_prefix,
                 RNA_struct_identifier(ptr->type),
                 RNA_property_identifier(prop));
    return -1;
  }
  else if ((RNA_property_flag(prop) & PROP_DYNAMIC) && lvalue_dim == 0) {
    if (RNA_property_array_length(ptr, prop) != tot) {
#if 0
      /* length is flexible */
      if (!RNA_property_dynamic_array_set_length(ptr, prop, tot)) {
        /* BLI_snprintf(error_str, error_str_size,
         *              "%s.%s: array length cannot be changed to %d",
         *              RNA_struct_identifier(ptr->type), RNA_property_identifier(prop), tot); */
        PyErr_Format(PyExc_ValueError,
                     "%s %s.%s: array length cannot be changed to %d",
                     error_prefix,
                     RNA_struct_identifier(ptr->type),
                     RNA_property_identifier(prop),
                     tot);
        return -1;
      }
#else
      *r_totitem = tot;
      return 0;

#endif
    }

    len = tot;
  }
  else {
    /* length is a constraint */
    if (!lvalue_dim) {
      len = RNA_property_array_length(ptr, prop);
    }
    /* array item assignment */
    else {
      int i;

      len = 1;

      /* arr[3][4][5]
       *
       *    arr[2] = x
       *    dimsize = {4, 5}
       *    dimsize[1] = 4
       *    dimsize[2] = 5
       *    lvalue_dim = 0, totdim = 3
       *
       *    arr[2][3] = x
       *    lvalue_dim = 1
       *
       *    arr[2][3][4] = x
       *    lvalue_dim = 2 */
      for (i = lvalue_dim; i < totdim; i++) {
        len *= dimsize[i];
      }
    }

    if (tot != len) {
      /* BLI_snprintf(error_str, error_str_size, "sequence must have length of %d", len); */
      PyErr_Format(PyExc_ValueError,
                   "%s %.200s.%.200s, sequence must have %d items total, not %d",
                   error_prefix,
                   RNA_struct_identifier(ptr->type),
                   RNA_property_identifier(prop),
                   len,
                   tot);
      return -1;
    }
  }

  *r_totitem = len;

  return 0;
}

static int validate_array(PyObject *rvalue,
                          PointerRNA *ptr,
                          PropertyRNA *prop,
                          int lvalue_dim,
                          ItemTypeCheckFunc check_item_type,
                          const char *item_type_str,
                          int *r_totitem,
                          const char *error_prefix)
{
  int dimsize[MAX_ARRAY_DIMENSION];
  int totdim = RNA_property_array_dimension(ptr, prop, dimsize);

  /* validate type first because length validation may modify property array length */

#ifdef USE_MATHUTILS
  if (lvalue_dim == 0) { /* only valid for first level array */
    if (MatrixObject_Check(rvalue)) {
      MatrixObject *pymat = (MatrixObject *)rvalue;

      if (BaseMath_ReadCallback(pymat) == -1) {
        return -1;
      }

      if (RNA_property_type(prop) != PROP_FLOAT) {
        PyErr_Format(PyExc_ValueError,
                     "%s %.200s.%.200s, matrix assign to non float array",
                     error_prefix,
                     RNA_struct_identifier(ptr->type),
                     RNA_property_identifier(prop));
        return -1;
      }
      else if (totdim != 2) {
        PyErr_Format(PyExc_ValueError,
                     "%s %.200s.%.200s, matrix assign array with %d dimensions",
                     error_prefix,
                     RNA_struct_identifier(ptr->type),
                     RNA_property_identifier(prop),
                     totdim);
        return -1;
      }
      else if (pymat->num_col != dimsize[0] || pymat->num_row != dimsize[1]) {
        PyErr_Format(PyExc_ValueError,
                     "%s %.200s.%.200s, matrix assign dimension size mismatch, "
                     "is %dx%d, expected be %dx%d",
                     error_prefix,
                     RNA_struct_identifier(ptr->type),
                     RNA_property_identifier(prop),
                     pymat->num_col,
                     pymat->num_row,
                     dimsize[0],
                     dimsize[1]);
        return -1;
      }
      else {
        *r_totitem = dimsize[0] * dimsize[1];
        return 0;
      }
    }
  }
#endif /* USE_MATHUTILS */

  {
    const int prop_flag = RNA_property_flag(prop);
    if (validate_array_type(rvalue,
                            lvalue_dim,
                            totdim,
                            dimsize,
                            (prop_flag & PROP_DYNAMIC) != 0,
                            check_item_type,
                            item_type_str,
                            error_prefix) == -1) {
      return -1;
    }

    return validate_array_length(rvalue, ptr, prop, lvalue_dim, r_totitem, error_prefix);
  }
}

static char *copy_value_single(PyObject *item,
                               PointerRNA *ptr,
                               PropertyRNA *prop,
                               char *data,
                               uint item_size,
                               int *index,
                               const ItemConvert_FuncArg *convert_item,
                               RNA_SetIndexFunc rna_set_index)
{
  if (!data) {
    union {
      float fl;
      int i;
    } value_buf;
    char *value = (void *)&value_buf;

    convert_item->func(&convert_item->arg, item, value);
    rna_set_index(ptr, prop, *index, value);
    (*index) += 1;
  }
  else {
    convert_item->func(&convert_item->arg, item, data);
    data += item_size;
  }

  return data;
}

static char *copy_values(PyObject *seq,
                         PointerRNA *ptr,
                         PropertyRNA *prop,
                         int dim,
                         char *data,
                         uint item_size,
                         int *index,
                         const ItemConvert_FuncArg *convert_item,
                         RNA_SetIndexFunc rna_set_index)
{
  int totdim = RNA_property_array_dimension(ptr, prop, NULL);
  const Py_ssize_t seq_size = PySequence_Size(seq);
  Py_ssize_t i;

  /* Regarding PySequence_GetItem() failing.
   *
   * This should never be NULL since we validated it, _but_ some tricky python
   * developer could write their own sequence type which succeeds on
   * validating but fails later somehow, so include checks for safety.
   */

  /* Note that 'data can be NULL' */

  if (seq_size == -1) {
    return NULL;
  }

#ifdef USE_MATHUTILS
  if (dim == 0) {
    if (MatrixObject_Check(seq)) {
      MatrixObject *pymat = (MatrixObject *)seq;
      size_t allocsize = pymat->num_col * pymat->num_row * sizeof(float);

      /* read callback already done by validate */
      /* since this is the first iteration we can assume data is allocated */
      memcpy(data, pymat->matrix, allocsize);

      /* not really needed but do for completeness */
      data += allocsize;

      return data;
    }
  }
#endif /* USE_MATHUTILS */

  for (i = 0; i < seq_size; i++) {
    PyObject *item = PySequence_GetItem(seq, i);
    if (item) {
      if (dim + 1 < totdim) {
        data = copy_values(
            item, ptr, prop, dim + 1, data, item_size, index, convert_item, rna_set_index);
      }
      else {
        data = copy_value_single(
            item, ptr, prop, data, item_size, index, convert_item, rna_set_index);
      }

      Py_DECREF(item);

      /* data may be NULL, but the for loop checks */
    }
    else {
      return NULL;
    }
  }

  return data;
}

static int py_to_array(PyObject *seq,
                       PointerRNA *ptr,
                       PropertyRNA *prop,
                       char *param_data,
                       ItemTypeCheckFunc check_item_type,
                       const char *item_type_str,
                       int item_size,
                       const ItemConvert_FuncArg *convert_item,
                       RNA_SetArrayFunc rna_set_array,
                       const char *error_prefix)
{
  /*int totdim, dim_size[MAX_ARRAY_DIMENSION];*/
  int totitem;
  char *data = NULL;

  /*totdim = RNA_property_array_dimension(ptr, prop, dim_size);*/ /*UNUSED*/

  if (validate_array(seq, ptr, prop, 0, check_item_type, item_type_str, &totitem, error_prefix) ==
      -1) {
    return -1;
  }

  if (totitem) {
    /* note: this code is confusing */
    if (param_data && RNA_property_flag(prop) & PROP_DYNAMIC) {
      /* not freeing allocated mem, RNA_parameter_list_free() will do this */
      ParameterDynAlloc *param_alloc = (ParameterDynAlloc *)param_data;
      param_alloc->array_tot = (int)totitem;

      /* freeing param list will free */
      param_alloc->array = MEM_callocN(item_size * totitem, "py_to_array dyn");

      data = param_alloc->array;
    }
    else if (param_data) {
      data = param_data;
    }
    else {
      data = PyMem_MALLOC(item_size * totitem);
    }

    /* will only fail in very rare cases since we already validated the
     * python data, the check here is mainly for completeness. */
    if (copy_values(seq, ptr, prop, 0, data, item_size, NULL, convert_item, NULL) != NULL) {
      if (param_data == NULL) {
        /* NULL can only pass through in case RNA property arraylength is 0 (impossible?) */
        rna_set_array(ptr, prop, data);
        PyMem_FREE(data);
      }
    }
    else {
      if (param_data == NULL) {
        PyMem_FREE(data);
      }

      PyErr_Format(PyExc_TypeError,
                   "%s internal error parsing sequence of type '%s' after successful validation",
                   error_prefix,
                   Py_TYPE(seq)->tp_name);
      return -1;
    }
  }

  return 0;
}

static int py_to_array_index(PyObject *py,
                             PointerRNA *ptr,
                             PropertyRNA *prop,
                             int lvalue_dim,
                             int arrayoffset,
                             int index,
                             ItemTypeCheckFunc check_item_type,
                             const char *item_type_str,
                             const ItemConvert_FuncArg *convert_item,
                             RNA_SetIndexFunc rna_set_index,
                             const char *error_prefix)
{
  int totdim, dimsize[MAX_ARRAY_DIMENSION];
  int totitem, i;

  totdim = RNA_property_array_dimension(ptr, prop, dimsize);

  /* convert index */

  /* arr[3][4][5]
   *
   *    arr[2] = x
   *    lvalue_dim = 0, index = 0 + 2 * 4 * 5
   *
   *    arr[2][3] = x
   *    lvalue_dim = 1, index = 40 + 3 * 5 */

  lvalue_dim++;

  for (i = lvalue_dim; i < totdim; i++) {
    index *= dimsize[i];
  }

  index += arrayoffset;

  if (lvalue_dim == totdim) { /* single item, assign directly */
    if (!check_item_type(py)) {
      PyErr_Format(PyExc_TypeError,
                   "%s %.200s.%.200s, expected a %s type, not %s",
                   error_prefix,
                   RNA_struct_identifier(ptr->type),
                   RNA_property_identifier(prop),
                   item_type_str,
                   Py_TYPE(py)->tp_name);
      return -1;
    }
    copy_value_single(py, ptr, prop, NULL, 0, &index, convert_item, rna_set_index);
  }
  else {
    if (validate_array(
            py, ptr, prop, lvalue_dim, check_item_type, item_type_str, &totitem, error_prefix) ==
        -1) {
      return -1;
    }

    if (totitem) {
      copy_values(py, ptr, prop, lvalue_dim, NULL, 0, &index, convert_item, rna_set_index);
    }
  }
  return 0;
}

static void py_to_float(const struct ItemConvertArgData *arg, PyObject *py, char *data)
{
  const float *range = arg->float_data.range;
  float value = (float)PyFloat_AsDouble(py);
  CLAMP(value, range[0], range[1]);
  *(float *)data = value;
}

static void py_to_int(const struct ItemConvertArgData *arg, PyObject *py, char *data)
{
  const int *range = arg->int_data.range;
  int value = PyC_Long_AsI32(py);
  CLAMP(value, range[0], range[1]);
  *(int *)data = value;
}

static void py_to_bool(const struct ItemConvertArgData *UNUSED(arg), PyObject *py, char *data)
{
  *(bool *)data = (bool)PyObject_IsTrue(py);
}

static int py_float_check(PyObject *py)
{
  /* accept both floats and integers */
  return PyNumber_Check(py);
}

static int py_int_check(PyObject *py)
{
  /* accept only integers */
  return PyLong_Check(py);
}

static int py_bool_check(PyObject *py)
{
  return PyBool_Check(py);
}

static void float_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, void *value)
{
  RNA_property_float_set_index(ptr, prop, index, *(float *)value);
}

static void int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, void *value)
{
  RNA_property_int_set_index(ptr, prop, index, *(int *)value);
}

static void bool_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, void *value)
{
  RNA_property_boolean_set_index(ptr, prop, index, *(bool *)value);
}

static void convert_item_init_float(PointerRNA *ptr,
                                    PropertyRNA *prop,
                                    ItemConvert_FuncArg *convert_item)
{
  float *range = convert_item->arg.float_data.range;
  convert_item->func = py_to_float;
  RNA_property_float_range(ptr, prop, &range[0], &range[1]);
}

static void convert_item_init_int(PointerRNA *ptr,
                                  PropertyRNA *prop,
                                  ItemConvert_FuncArg *convert_item)
{
  int *range = convert_item->arg.int_data.range;
  convert_item->func = py_to_int;
  RNA_property_int_range(ptr, prop, &range[0], &range[1]);
}

static void convert_item_init_bool(PointerRNA *UNUSED(ptr),
                                   PropertyRNA *UNUSED(prop),
                                   ItemConvert_FuncArg *convert_item)
{
  convert_item->func = py_to_bool;
}

int pyrna_py_to_array(
    PointerRNA *ptr, PropertyRNA *prop, char *param_data, PyObject *py, const char *error_prefix)
{
  int ret;
  switch (RNA_property_type(prop)) {
    case PROP_FLOAT: {
      ItemConvert_FuncArg convert_item;
      convert_item_init_float(ptr, prop, &convert_item);

      ret = py_to_array(py,
                        ptr,
                        prop,
                        param_data,
                        py_float_check,
                        "float",
                        sizeof(float),
                        &convert_item,
                        (RNA_SetArrayFunc)RNA_property_float_set_array,
                        error_prefix);
      break;
    }
    case PROP_INT: {
      ItemConvert_FuncArg convert_item;
      convert_item_init_int(ptr, prop, &convert_item);

      ret = py_to_array(py,
                        ptr,
                        prop,
                        param_data,
                        py_int_check,
                        "int",
                        sizeof(int),
                        &convert_item,
                        (RNA_SetArrayFunc)RNA_property_int_set_array,
                        error_prefix);
      break;
    }
    case PROP_BOOLEAN: {
      ItemConvert_FuncArg convert_item;
      convert_item_init_bool(ptr, prop, &convert_item);

      ret = py_to_array(py,
                        ptr,
                        prop,
                        param_data,
                        py_bool_check,
                        "boolean",
                        sizeof(bool),
                        &convert_item,
                        (RNA_SetArrayFunc)RNA_property_boolean_set_array,
                        error_prefix);
      break;
    }
    default: {
      PyErr_SetString(PyExc_TypeError, "not an array type");
      ret = -1;
      break;
    }
  }

  return ret;
}

int pyrna_py_to_array_index(PointerRNA *ptr,
                            PropertyRNA *prop,
                            int arraydim,
                            int arrayoffset,
                            int index,
                            PyObject *py,
                            const char *error_prefix)
{
  int ret;
  switch (RNA_property_type(prop)) {
    case PROP_FLOAT: {
      ItemConvert_FuncArg convert_item;
      convert_item_init_float(ptr, prop, &convert_item);

      ret = py_to_array_index(py,
                              ptr,
                              prop,
                              arraydim,
                              arrayoffset,
                              index,
                              py_float_check,
                              "float",
                              &convert_item,
                              float_set_index,
                              error_prefix);
      break;
    }
    case PROP_INT: {
      ItemConvert_FuncArg convert_item;
      convert_item_init_int(ptr, prop, &convert_item);

      ret = py_to_array_index(py,
                              ptr,
                              prop,
                              arraydim,
                              arrayoffset,
                              index,
                              py_int_check,
                              "int",
                              &convert_item,
                              int_set_index,
                              error_prefix);
      break;
    }
    case PROP_BOOLEAN: {
      ItemConvert_FuncArg convert_item;
      convert_item_init_bool(ptr, prop, &convert_item);

      ret = py_to_array_index(py,
                              ptr,
                              prop,
                              arraydim,
                              arrayoffset,
                              index,
                              py_bool_check,
                              "boolean",
                              &convert_item,
                              bool_set_index,
                              error_prefix);
      break;
    }
    default: {
      PyErr_SetString(PyExc_TypeError, "not an array type");
      ret = -1;
      break;
    }
  }

  return ret;
}

PyObject *pyrna_array_index(PointerRNA *ptr, PropertyRNA *prop, int index)
{
  PyObject *item;

  switch (RNA_property_type(prop)) {
    case PROP_FLOAT:
      item = PyFloat_FromDouble(RNA_property_float_get_index(ptr, prop, index));
      break;
    case PROP_BOOLEAN:
      item = PyBool_FromLong(RNA_property_boolean_get_index(ptr, prop, index));
      break;
    case PROP_INT:
      item = PyLong_FromLong(RNA_property_int_get_index(ptr, prop, index));
      break;
    default:
      PyErr_SetString(PyExc_TypeError, "not an array type");
      item = NULL;
      break;
  }

  return item;
}

#if 0
/* XXX this is not used (and never will?) */
/* Given an array property, creates an N-dimensional tuple of values. */
static PyObject *pyrna_py_from_array_internal(PointerRNA *ptr,
                                              PropertyRNA *prop,
                                              int dim,
                                              int *index)
{
  PyObject *tuple;
  int i, len;
  int totdim = RNA_property_array_dimension(ptr, prop, NULL);

  len = RNA_property_multi_array_length(ptr, prop, dim);

  tuple = PyTuple_New(len);

  for (i = 0; i < len; i++) {
    PyObject *item;

    if (dim + 1 < totdim) {
      item = pyrna_py_from_array_internal(ptr, prop, dim + 1, index);
    }
    else {
      item = pyrna_array_index(ptr, prop, *index);
      *index = *index + 1;
    }

    if (!item) {
      Py_DECREF(tuple);
      return NULL;
    }

    PyTuple_SET_ITEM(tuple, i, item);
  }

  return tuple;
}
#endif

PyObject *pyrna_py_from_array_index(BPy_PropertyArrayRNA *self,
                                    PointerRNA *ptr,
                                    PropertyRNA *prop,
                                    int index)
{
  int totdim, arraydim, arrayoffset, dimsize[MAX_ARRAY_DIMENSION], i, len;
  BPy_PropertyArrayRNA *ret = NULL;

  arraydim = self ? self->arraydim : 0;
  arrayoffset = self ? self->arrayoffset : 0;

  /* just in case check */
  len = RNA_property_multi_array_length(ptr, prop, arraydim);
  if (index >= len || index < 0) {
    /* this shouldn't happen because higher level funcs must check for invalid index */
    CLOG_WARN(BPY_LOG_RNA, "invalid index %d for array with length=%d", index, len);

    PyErr_SetString(PyExc_IndexError, "out of range");
    return NULL;
  }

  totdim = RNA_property_array_dimension(ptr, prop, dimsize);

  if (arraydim + 1 < totdim) {
    ret = (BPy_PropertyArrayRNA *)pyrna_prop_CreatePyObject(ptr, prop);
    ret->arraydim = arraydim + 1;

    /* arr[3][4][5]
     *
     *    x = arr[2]
     *    index = 0 + 2 * 4 * 5
     *
     *    x = arr[2][3]
     *    index = offset + 3 * 5 */

    for (i = arraydim + 1; i < totdim; i++) {
      index *= dimsize[i];
    }

    ret->arrayoffset = arrayoffset + index;
  }
  else {
    index = arrayoffset + index;
    ret = (BPy_PropertyArrayRNA *)pyrna_array_index(ptr, prop, index);
  }

  return (PyObject *)ret;
}

PyObject *pyrna_py_from_array(PointerRNA *ptr, PropertyRNA *prop)
{
  PyObject *ret;

  ret = pyrna_math_object_from_array(ptr, prop);

  /* is this a maths object? */
  if (ret) {
    return ret;
  }

  return pyrna_prop_CreatePyObject(ptr, prop);
}

/* TODO, multi-dimensional arrays */
int pyrna_array_contains_py(PointerRNA *ptr, PropertyRNA *prop, PyObject *value)
{
  int len = RNA_property_array_length(ptr, prop);
  int type;
  int i;

  if (len == 0) {
    /* possible with dynamic arrays */
    return 0;
  }

  if (RNA_property_array_dimension(ptr, prop, NULL) > 1) {
    PyErr_SetString(PyExc_TypeError, "PropertyRNA - multi dimensional arrays not supported yet");
    return -1;
  }

  type = RNA_property_type(prop);

  switch (type) {
    case PROP_FLOAT: {
      float value_f = PyFloat_AsDouble(value);
      if (value_f == -1 && PyErr_Occurred()) {
        PyErr_Clear();
        return 0;
      }
      else {
        float tmp[32];
        float *tmp_arr;

        if (len * sizeof(float) > sizeof(tmp)) {
          tmp_arr = PyMem_MALLOC(len * sizeof(float));
        }
        else {
          tmp_arr = tmp;
        }

        RNA_property_float_get_array(ptr, prop, tmp_arr);

        for (i = 0; i < len; i++) {
          if (tmp_arr[i] == value_f) {
            break;
          }
        }

        if (tmp_arr != tmp) {
          PyMem_FREE(tmp_arr);
        }

        return i < len ? 1 : 0;
      }
      break;
    }
    case PROP_INT: {
      int value_i = PyC_Long_AsI32(value);
      if (value_i == -1 && PyErr_Occurred()) {
        PyErr_Clear();
        return 0;
      }
      else {
        int tmp[32];
        int *tmp_arr;

        if (len * sizeof(int) > sizeof(tmp)) {
          tmp_arr = PyMem_MALLOC(len * sizeof(int));
        }
        else {
          tmp_arr = tmp;
        }

        RNA_property_int_get_array(ptr, prop, tmp_arr);

        for (i = 0; i < len; i++) {
          if (tmp_arr[i] == value_i) {
            break;
          }
        }

        if (tmp_arr != tmp) {
          PyMem_FREE(tmp_arr);
        }

        return i < len ? 1 : 0;
      }
      break;
    }
    case PROP_BOOLEAN: {
      int value_i = PyC_Long_AsBool(value);
      if (value_i == -1 && PyErr_Occurred()) {
        PyErr_Clear();
        return 0;
      }
      else {
        bool tmp[32];
        bool *tmp_arr;

        if (len * sizeof(bool) > sizeof(tmp)) {
          tmp_arr = PyMem_MALLOC(len * sizeof(bool));
        }
        else {
          tmp_arr = tmp;
        }

        RNA_property_boolean_get_array(ptr, prop, tmp_arr);

        for (i = 0; i < len; i++) {
          if (tmp_arr[i] == value_i) {
            break;
          }
        }

        if (tmp_arr != tmp) {
          PyMem_FREE(tmp_arr);
        }

        return i < len ? 1 : 0;
      }
      break;
    }
  }

  /* should never reach this */
  PyErr_SetString(PyExc_TypeError, "PropertyRNA - type not in float/bool/int");
  return -1;
}
