/*
 * This file implements the API for the array type.
 *
 * Copyright (c) 2019 Riverbank Computing Limited <info@riverbankcomputing.com>
 *
 * This file is part of SIP.
 *
 * This copy of SIP is licensed for use under the terms of the SIP License
 * Agreement.  See the file LICENSE for more details.
 *
 * This copy of SIP may also used under the terms of the GNU General Public
 * License v2 or v3 as published by the Free Software Foundation which can be
 * found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
 *
 * SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */


#include <Python.h>

#include <stddef.h>
#include <string.h>

#include "sipint.h"

#include "array.h"


/* The object data structure. */
typedef struct {
    PyObject_HEAD
    void *data;
    const sipTypeDef *td;
    const char *format;
    size_t stride;
    Py_ssize_t len;
    int flags;
    PyObject *owner;
} sipArrayObject;


static int check_writable(sipArrayObject *array);
static int check_index(sipArrayObject *array, Py_ssize_t idx);
static void *get_value(sipArrayObject *array, PyObject *value);
static void *get_slice(sipArrayObject *array, PyObject *value, Py_ssize_t len);
static void bad_key(PyObject *key);
static void *element(sipArrayObject *array, Py_ssize_t idx);
static PyObject *make_array(void *data, const sipTypeDef *td,
        const char *format, size_t stride, Py_ssize_t len, int flags,
        PyObject *owner);


/*
 * Implement len() for the type.
 */
static Py_ssize_t sipArray_length(PyObject *self)
{
    return ((sipArrayObject *)self)->len;
}


/*
 * Implement sequence item sub-script for the type.
 */
static PyObject *sipArray_item(PyObject *self, Py_ssize_t idx)
{
    sipArrayObject *array = (sipArrayObject *)self;
    PyObject *py_item;
    void *data;

    if (check_index(array, idx) < 0)
        return NULL;

    data = element(array, idx);

    if (array->td != NULL)
    {
        py_item = sip_api_convert_from_type(data, array->td, NULL);
    }
    else
    {
        switch (*array->format)
        {
        case 'b':
            py_item = PyLong_FromLong(*(char *)data);
            break;

        case 'B':
            py_item = PyLong_FromUnsignedLong(*(unsigned char *)data);
            break;

        case 'h':
            py_item = PyLong_FromLong(*(short *)data);
            break;

        case 'H':
            py_item = PyLong_FromUnsignedLong(*(unsigned short *)data);
            break;

        case 'i':
            py_item = PyLong_FromLong(*(int *)data);
            break;

        case 'I':
            py_item = PyLong_FromUnsignedLong(*(unsigned int *)data);
            break;

        case 'f':
            py_item = PyFloat_FromDouble(*(float *)data);
            break;

        case 'd':
            py_item = PyFloat_FromDouble(*(double *)data);
            break;

        default:
            py_item = NULL;
        }
    }

    return py_item;
}


/* The sequence methods data structure. */
static PySequenceMethods sipArray_SequenceMethods = {
    sipArray_length,        /* sq_length */
    0,                      /* sq_concat */
    0,                      /* sq_repeat */
    sipArray_item,          /* sq_item */
    0,                      /* sq_slice */
    0,                      /* sq_ass_item */
    0,                      /* sq_ass_slice */
    0,                      /* sq_contains */
    0,                      /* sq_inplace_concat */
    0,                      /* sq_inplace_repeat */
};


/*
 * Implement mapping sub-script for the type.
 */
static PyObject *sipArray_subscript(PyObject *self, PyObject *key)
{
    sipArrayObject *array = (sipArrayObject *)self;

    if (PyIndex_Check(key))
    {
        Py_ssize_t idx = PyNumber_AsSsize_t(key, PyExc_IndexError);

        if (idx == -1 && PyErr_Occurred())
            return NULL;

        if (idx < 0)
            idx += array->len;

        return sipArray_item(self, idx);
    }

    if (PySlice_Check(key))
    {
        Py_ssize_t start, stop, step, slicelength;

        if (sip_api_convert_from_slice_object(key, array->len, &start, &stop, &step, &slicelength) < 0)
            return NULL;

        if (step != 1)
        {
            PyErr_SetNone(PyExc_NotImplementedError);
            return NULL;
        }

        return make_array(element(array->data, start), array->td,
                array->format, array->stride, slicelength,
                (array->flags & ~SIP_OWNS_MEMORY), array->owner);
    }

    bad_key(key);

    return NULL;
}


/*
 * Implement mapping assignment sub-script for the type.
 */
static int sipArray_ass_subscript(PyObject *self, PyObject *key,
        PyObject *value)
{
    sipArrayObject *array = (sipArrayObject *)self;
    Py_ssize_t start, len;
    void *value_data;

    if (check_writable(array) < 0)
        return -1;

    if (PyIndex_Check(key))
    {
        start = PyNumber_AsSsize_t(key, PyExc_IndexError);

        if (start == -1 && PyErr_Occurred())
            return -1;

        if (start < 0)
            start += array->len;

        if (check_index(array, start) < 0)
            return -1;

        if ((value_data = get_value(array, value)) == NULL)
            return -1;

        len = 1;
    }
    else if (PySlice_Check(key))
    {
        Py_ssize_t stop, step;

        if (sip_api_convert_from_slice_object(key, array->len, &start, &stop, &step, &len) < 0)
            return -1;

        if (step != 1)
        {
            PyErr_SetNone(PyExc_NotImplementedError);
            return -1;
        }

        if ((value_data = get_slice(array, value, len)) == NULL)
            return -1;
    }
    else
    {
        bad_key(key);

        return -1;
    }

    memmove(element(array, start), value_data, len * array->stride);

    return 0;
}


/* The mapping methods data structure. */
static PyMappingMethods sipArray_MappingMethods = {
    sipArray_length,        /* mp_length */
    sipArray_subscript,     /* mp_subscript */
    sipArray_ass_subscript, /* mp_ass_subscript */
};


/*
 * The buffer implementation for Python v2.6.3 and later.
 */
static int sipArray_getbuffer(PyObject *self, Py_buffer *view, int flags)
{
    sipArrayObject *array = (sipArrayObject *)self;

    if (view == NULL)
        return 0;

    if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && (array->flags & SIP_READ_ONLY))
    {
        PyErr_SetString(PyExc_BufferError, "object is not writable.");
        return -1;
    }

    view->obj = self;
    Py_INCREF(self);

    view->buf = array->data;
    view->len = array->len;
    view->readonly = (array->flags & SIP_READ_ONLY);
    view->itemsize = array->stride;

    view->format = NULL;
    if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
        view->format = (char *)array->format;

    view->ndim = 1;

    view->shape = NULL;
    if ((flags & PyBUF_ND) == PyBUF_ND)
        view->shape = &view->len;

    view->strides = NULL;
    if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES)
        view->strides = &view->itemsize;

    view->suboffsets = NULL;
    view->internal = NULL;

    return 0;
}


/* The buffer methods data structure. */
static PyBufferProcs sipArray_BufferProcs = {
    sipArray_getbuffer,     /* bf_getbuffer */
    0                       /* bf_releasebuffer */
};


/* The instance deallocation function. */
static void sipArray_dealloc(PyObject *self)
{
    sipArrayObject *array = (sipArrayObject *)self;

    if (array->flags & SIP_OWNS_MEMORY)
        sip_api_free(array->data);
    else
        Py_XDECREF(array->owner);
}


/* The type data structure. */
PyTypeObject sipArray_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "sip.array",            /* tp_name */
    sizeof (sipArrayObject),    /* tp_basicsize */
    0,                      /* tp_itemsize */
    sipArray_dealloc,       /* tp_dealloc */
    0,                      /* tp_print */
    0,                      /* tp_getattr */
    0,                      /* tp_setattr */
    0,                      /* tp_reserved */
    0,                      /* tp_repr */
    0,                      /* tp_as_number */
    &sipArray_SequenceMethods,  /* tp_as_sequence */
    &sipArray_MappingMethods,   /* tp_as_mapping */
    0,                      /* tp_hash */
    0,                      /* tp_call */
    0,                      /* tp_str */
    0,                      /* tp_getattro */
    0,                      /* tp_setattro */
    &sipArray_BufferProcs,  /* tp_as_buffer */
#if defined(Py_TPFLAGS_HAVE_NEWBUFFER)
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER,   /* tp_flags */
#else
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */
#endif
    0,                      /* tp_doc */
    0,                      /* tp_traverse */
    0,                      /* tp_clear */
    0,                      /* tp_richcompare */
    0,                      /* tp_weaklistoffset */
    0,                      /* tp_iter */
    0,                      /* tp_iternext */
    0,                      /* tp_methods */
    0,                      /* tp_members */
    0,                      /* tp_getset */
    0,                      /* tp_base */
    0,                      /* tp_dict */
    0,                      /* tp_descr_get */
    0,                      /* tp_descr_set */
    0,                      /* tp_dictoffset */
    0,                      /* tp_init */
    0,                      /* tp_alloc */
    0,                      /* tp_new */
    0,                      /* tp_free */
    0,                      /* tp_is_gc */
    0,                      /* tp_bases */
    0,                      /* tp_mro */
    0,                      /* tp_cache */
    0,                      /* tp_subclasses */
    0,                      /* tp_weaklist */
    0,                      /* tp_del */
    0,                      /* tp_version_tag */
    0,                      /* tp_finalize */
#if PY_VERSION_HEX >= 0x03080000
    0,                      /* tp_vectorcall */
#endif
};


/*
 * Check that an array is writable.
 */
static int check_writable(sipArrayObject *array)
{
    if (array->flags & SIP_READ_ONLY)
    {
        PyErr_SetString(PyExc_TypeError, "sip.array object is read-only");
        return -1;
    }

    return 0;
}


/*
 * Check that an index is valid for an array.
 */
static int check_index(sipArrayObject *array, Py_ssize_t idx)
{
    if (idx >= 0 && idx < array->len)
        return 0;

    PyErr_SetString(PyExc_IndexError, "index out of bounds");

    return -1;
}


/*
 * Raise an exception about a bad sub-script key.
 */
static void bad_key(PyObject *key)
{
    PyErr_Format(PyExc_TypeError,
            "cannot index a sip.array object using '%s'",
            Py_TYPE(key)->tp_name);
}


/*
 * Get the address of an element of an array.
 */
static void *element(sipArrayObject *array, Py_ssize_t idx)
{
    return (unsigned char *)(array->data) + idx * array->stride;
}


/*
 * Get the address of a value that will be copied to an array.
 */
static void *get_value(sipArrayObject *array, PyObject *value)
{
    static union {
        signed char s_char_t;
        unsigned char u_char_t;
        signed short s_short_t;
        unsigned short u_short_t;
        signed int s_int_t;
        unsigned int u_int_t;
        float float_t;
        double double_t;
    } static_data;

    void *data;

    if (array->td != NULL)
    {
        int iserr = FALSE;

        data = sip_api_force_convert_to_type(value, array->td, NULL,
                SIP_NOT_NONE|SIP_NO_CONVERTORS, NULL, &iserr);
    }
    else
    {
        PyErr_Clear();

        switch (*array->format)
        {
        case 'b':
            static_data.s_char_t = sip_api_long_as_char(value);
            data = &static_data.s_char_t;
            break;

        case 'B':
            static_data.u_char_t = sip_api_long_as_unsigned_char(value);
            data = &static_data.u_char_t;
            break;

        case 'h':
            static_data.s_short_t = sip_api_long_as_short(value);
            data = &static_data.s_short_t;
            break;

        case 'H':
            static_data.u_short_t = sip_api_long_as_unsigned_short(value);
            data = &static_data.u_short_t;
            break;

        case 'i':
            static_data.s_int_t = sip_api_long_as_int(value);
            data = &static_data.s_int_t;
            break;

        case 'I':
            static_data.u_int_t = sip_api_long_as_unsigned_int(value);
            data = &static_data.u_int_t;
            break;

        case 'f':
            static_data.float_t = (float)PyFloat_AsDouble(value);
            data = &static_data.float_t;
            break;

        case 'd':
            static_data.double_t = PyFloat_AsDouble(value);
            data = &static_data.double_t;
            break;

        default:
            data = NULL;
        }

        if (PyErr_Occurred())
            data = NULL;
    }

    return data;
}


/*
 * Get the address of an value that will be copied to an array slice.
 */
static void *get_slice(sipArrayObject *array, PyObject *value, Py_ssize_t len)
{
    sipArrayObject *other = (sipArrayObject *)value;

    if (!PyObject_IsInstance(value, (PyObject *)&sipArray_Type) || array->td != other->td || strcmp(array->format, other->format) != 0)
    {
        const char *type;

        if (array->td != NULL)
        {
            type = sipTypeName(array->td);
        }
        else
        {
            switch (*array->format)
            {
            case 'b':
                type = "char";
                break;

            case 'B':
                type = "unsigned char";
                break;

            case 'h':
                type = "short";
                break;

            case 'H':
                type = "unsigned short";
                break;

            case 'i':
                type = "int";
                break;

            case 'I':
                type = "unsigned int";
                break;

            case 'f':
                type = "float";
                break;

            case 'd':
                type = "double";
                break;

            default:
                type = "";
            }
        }

        PyErr_Format(PyExc_TypeError,
                "can only assign another array of %s to the slice", type);

        return NULL;
    }

    if (other->len != len)
    {
        PyErr_Format(PyExc_TypeError,
                "the array being assigned must have length %zd", len);

        return NULL;
    }

    if (other->stride == array->stride)
    {
        PyErr_Format(PyExc_TypeError,
                "the array being assigned must have stride %zu",
                array->stride);

        return NULL;
    }

    return other->data;
}


/*
 * Do the work of creating an array.
 */
static PyObject *make_array(void *data, const sipTypeDef *td,
        const char *format, size_t stride, Py_ssize_t len, int flags,
        PyObject *owner)
{
    sipArrayObject *array;

    if ((array = PyObject_NEW(sipArrayObject, &sipArray_Type)) == NULL)
        return NULL;

    array->data = data;
    array->td = td;
    array->format = format;
    array->stride = stride;
    array->len = len;
    array->flags = flags;

    if (flags & SIP_OWNS_MEMORY)
    {
        /* This is a borrowed reference to itself. */
        array->owner = (PyObject *)array;
    }
    else
    {
        Py_XINCREF(owner);
        array->owner = owner;
    }

    return (PyObject *)array;
}


/*
 * Wrap an array of instances of a fundamental type.  At the moment format must
 * be either "b" (char), "B" (unsigned char), "h" (short), "H" (unsigned
 * short), "i" (int), "I" (unsigned int), "f" (float) or "d" (double).
 */
PyObject *sip_api_convert_to_array(void *data, const char *format,
        Py_ssize_t len, int flags)
{
    size_t stride;

    if (data == NULL)
    {
        Py_INCREF(Py_None);
        return Py_None;
    }

    switch (*format)
    {
    case 'b':
        stride = sizeof (char);
        break;

    case 'B':
        stride = sizeof (unsigned char);
        break;

    case 'h':
        stride = sizeof (short);
        break;

    case 'H':
        stride = sizeof (unsigned short);
        break;

    case 'i':
        stride = sizeof (int);
        break;

    case 'I':
        stride = sizeof (unsigned int);
        break;

    case 'f':
        stride = sizeof (float);
        break;

    case 'd':
        stride = sizeof (double);
        break;

    default:
        stride = 0;
    }

    assert(stride > 0);
    assert(len >= 0);

    return make_array(data, NULL, format, stride, len, flags, NULL);
}


/*
 * Wrap an array of instances of a defined type.
 */
PyObject *sip_api_convert_to_typed_array(void *data, const sipTypeDef *td,
        const char *format, size_t stride, Py_ssize_t len, int flags)
{
    if (data == NULL)
    {
        Py_INCREF(Py_None);
        return Py_None;
    }

    assert(stride > 0);
    assert(len >= 0);

    return make_array(data, td, format, stride, len, flags, NULL);
}
