/* Copyright 2000-2001 ActiveState
 *
 * svrv_objects encapsulate a perl SvRV().
 */

#include <EXTERN.h>
#include <perl.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif
#include "pycompat.h"
#include "thrd_ctx.h"
#include "svrv_object.h"
#include "perlmodule.h"
#include "lang_lock.h"
#include "lang_map.h"
#include "try_perlapi.h"

#ifdef MULTI_PERL
static int
owned_by(PySVRV *self, refcounted_perl *my_perl)
{
    if (self->owned_by != my_perl) {
	PyErr_SetString(PyExc_ValueError,
			"perl reference accessed in wrong thread");
	return 0;
    }
    return 1;
}

#define CHECK_OWNED(ret) do { \
                            ASSERT_LOCK_PYTHON; \
                            if (!owned_by(self, ctx->perl)) \
                                return (ret); \
                         } while (0)

#define CHECK_OWNED_PY  CHECK_OWNED((PyObject*)NULL)
#define CHECK_OWNED_INT CHECK_OWNED(-1)



#else /* MULTI_PERL */

#define CHECK_OWNED     /* empty */
#define CHECK_OWNED_PY  /* empty */
#define CHECK_OWNED_INT /* empty */

#endif /* MULTI_PERL */


PyObject*
PySVRV_New(SV* rv)
{
	dCTXP;
    PySVRV *self;
    ASSERT_LOCK_BOTH;
    self = PyObject_NEW(PySVRV, &SVRVtype);
    if (self == NULL)
	return NULL;
    self->rv = SvREFCNT_inc(rv);
#ifdef MULTI_PERL
	self->owned_by = ctx->perl;
	ctx->perl->refcnt++;
#endif
    self->methodname = NULL;
    self->gimme = G_SCALAR;
    /* printf("created svrv object %p\n", self); */
    return (PyObject*)self;
}


static void
pysvrv_dealloc(PySVRV *self)
{
    /* printf("dead svrv object %p\n", self); */
    dCTXP;
#ifdef MULTI_PERL
    PerlInterpreter *old_perl = 0;
    if (my_perl != self->owned_by->my_perl) {
	old_perl = my_perl;
	my_perl = self->owned_by->my_perl;
	PERL_SET_CONTEXT(my_perl);
    }
#endif

    ASSERT_LOCK_PYTHON;
    ENTER_PERL;
    SvREFCNT_dec(self->rv);
    Safefree(self->methodname);

#ifdef MULTI_PERL
    if (old_perl)
	PERL_SET_CONTEXT(old_perl);

    if (--self->owned_by->refcnt == 0) {
	if (self->owned_by->thread_done) {
	    free_perl(self->owned_by->my_perl);
	    self->owned_by->my_perl = 0;
	    ENTER_PYTHON;
	    PyMem_Free((char*)(self->owned_by));
	    ENTER_PERL;
	}
    }
    self->owned_by = 0;
#endif

    ENTER_PYTHON;
    PyObject_DEL(self);
    ASSERT_LOCK_PYTHON;
}


static PyObject*
pysvrv_has_key(PySVRV *self, PyObject *args)
{
    char *key;
    Py_ssize_t keylen;
    int exists;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_ParseTuple(args, "s#:has_key", &key, &keylen))
	return NULL;

    ENTER_PERL;
    SET_CUR_PERL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVHV);
    exists = hv_exists((HV*)SvRV(self->rv), key, keylen);

    ENTER_PYTHON;
    return PyBool_FromLong(exists);
}

static PyObject*
do_hash_kv(HV* hv, bool do_keys, bool do_values)
{
    /* assumes we have the python lock only on entry */
    register HE *entry;
    register PyObject* list;
    int i;
    int len;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    assert(do_keys || do_values);

    ENTER_PERL;
    SET_CUR_PERL;
    len = HvKEYS(hv);
  
    ENTER_PYTHON;
    list = PyList_New(len);
    if (list == NULL) {
	ASSERT_LOCK_PYTHON;
	return NULL;
    }

    ENTER_PERL;
    i = 0;
    hv_iterinit(hv);
    while ( (entry = hv_iternext(hv))) {
	PyObject *k;
	if (do_keys) {
	    I32 klen;
	    char *kstr = hv_iterkey(entry, &klen);
	    ENTER_PYTHON;
	    k = PyUnicode_FromStringAndSize(kstr, klen);
	    if (k == NULL)
		goto FAIL;
	    ENTER_PERL;
	}
	if (do_values) {
	    SV* val_sv = hv_iterval(hv, entry);
	    PyObject *v;

	    ENTER_PYTHON;
	    PERL_LOCK;
	    v = sv2pyo(val_sv);
	    PERL_UNLOCK;
	    if (do_keys) {
		PyObject *t = PyTuple_New(2);
		if (t == NULL) {
		    if (do_keys) Py_DECREF(k);
		    goto FAIL;
		}
		/* These can't fail :-) */
		PyTuple_SetItem(t, 0, k);
		PyTuple_SetItem(t, 1, v);
		v = t;
	    }
	    if (PyList_SetItem(list, i, v) == -1) {
		Py_DECREF(v);
		goto FAIL;
	    }
	    ENTER_PERL;
	}
	else if (PyList_SetItem(list, i, k) == -1) {
	    ENTER_PYTHON;
	    Py_DECREF(k);
	    goto FAIL;
	};
	i++;
    }

    ENTER_PYTHON;
    return list;

FAIL:
    Py_DECREF(list);
    ASSERT_LOCK_PYTHON;
    return NULL;
}


static int
pysvrv_contains(PySVRV *self, PyObject *value)
{
    char *key;
    Py_ssize_t keylen;
    bool exists;
    PyObject *found;
    SV** svp;
    dCTXP;

    if (!PyUnicode_Check(value)) {
	PyErr_SetString(PyExc_TypeError, "perl hash key must be string");
	return -1;
    }

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    key = PyUnicode_AsUTF8AndSize(value, &keylen);

    ENTER_PERL;
    SET_CUR_PERL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVHV);
    exists = hv_exists((HV*)SvRV(self->rv), key, keylen);

    ENTER_PYTHON;
#if PY_MAJOR_VERSION < 3
    if (svp) {
	PERL_LOCK;
	found = sv2pyo(*svp);
	PERL_UNLOCK;
    } else {
	if (PyErr_Occurred()) {
	    Py_DECREF(key);
	    return -1;
	}
	return 0;
    }

    return PyObject_RichCompareBool(value, found, Py_EQ);
#else
    return exists;
#endif
}

static PyObject*
pysvrv_keys(PySVRV *self, PyObject *args)
{
    dCTXP;
    SET_CUR_PERL;
    CHECK_OWNED_PY;

    ASSERT_LOCK_PYTHON;
    if (!PyArg_Parse(args, ""))
	return NULL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVHV);
    return do_hash_kv((HV*)SvRV(self->rv), TRUE, FALSE);
}

static PyObject*
pysvrv_values(PySVRV *self, PyObject *args)
{ 
    dCTXP;
    SET_CUR_PERL;
    CHECK_OWNED_PY;

    ASSERT_LOCK_PYTHON;
    if (!PyArg_Parse(args, ""))
	return NULL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVHV);
    return do_hash_kv((HV*)SvRV(self->rv), FALSE, TRUE);
}

static PyObject*
pysvrv_items(PySVRV *self, PyObject *args)
{
#ifdef DEBUGGING
    dCTXP;
    SET_CUR_PERL;
#endif
    ASSERT_LOCK_PYTHON;
    if (!PyArg_Parse(args, ""))
	return NULL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVHV);
    return do_hash_kv((HV*)SvRV(self->rv), TRUE, TRUE);
}

static PyObject*
pysvrv_update(PySVRV *self, PyObject *args)
{
    PyObject *o;
    PyObject *items;
    int i;
    PyObject *elem;
    HV* hv;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_ParseTuple(args, "O:update", &o))
	goto FAIL;

    if (!PyMapping_Check(o)) {
	PyErr_SetString(PyExc_TypeError,
			"hash.update() argument must be a mapping object");
	goto FAIL;
    }

    items = PyMapping_Items(o);
    if (items == NULL)
	goto FAIL;

    if (!PyList_Check(items)) {
	Py_DECREF(items);
	PyErr_SetString(PyExc_SystemError,
			"PyMapping_Items did not return a list");
    }

    ENTER_PERL;
    SET_CUR_PERL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVHV);

    hv = (HV*)SvRV(self->rv);
    ENTER_PYTHON;

    for (i = 0; (elem = PyList_GetItem(items, i)); i++) {
	PyObject* key;
	PyObject* val;
	SV* key_sv;
	SV* val_sv;

	ASSERT_LOCK_PYTHON;
	if (!PySequence_Check(elem))
	    continue;
	key = PySequence_GetItem(elem, 0);
	if (!key) {
	    PyErr_Clear();
	    continue;
	}
	val = PySequence_GetItem(elem, 1);
	if (!val) {
	    PyErr_Clear();
	    continue;
	}

	PERL_LOCK;
	key_sv = pyo2sv(key);
	val_sv = pyo2sv(val);
	PYTHON_UNLOCK;

	if (!hv_store_ent(hv, key_sv, val_sv, 0))
	    SvREFCNT_dec(val_sv);
	SvREFCNT_dec(key_sv);
	ENTER_PYTHON;
    }
    PyErr_Clear();  /* index error */
    Py_DECREF(items);

    ASSERT_LOCK_PYTHON;
    Py_INCREF(Py_None);
    return Py_None;

FAIL:
    ASSERT_LOCK_PYTHON;
    return NULL;
}

static PyObject*
pysvrv_clear(PySVRV *self, PyObject *args)
{
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_Parse(args, ""))
	return NULL;

    ENTER_PERL;
    SET_CUR_PERL;

    assert(SvTYPE(SvRV(self->rv)) == SVt_PVHV);
    hv_clear((HV*)SvRV(self->rv));

    ENTER_PYTHON;
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject*
pysvrv_copy(PySVRV *self, PyObject *args)
{
    HV* hv;
    HV* new_hv;
    HE* entry;
    SV* sv;
    PyObject *pyo;
    dCTXP;


    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_Parse(args, ""))
	return NULL;

    ENTER_PERL;
    SET_CUR_PERL;

    assert(SvTYPE(SvRV(self->rv)) == SVt_PVHV);
    hv = (HV*)SvRV(self->rv);

    new_hv = newHV();
    hv_iterinit(hv);
    while ( (entry = hv_iternext(hv))) {
	sv = newSVsv(HeVAL(entry));
	if (!hv_store_ent(new_hv, hv_iterkeysv(entry), sv, 0))
	    SvREFCNT_dec(sv);
    }

    sv = newRV_noinc((SV*)new_hv);

    ENTER_PYTHON;
    PERL_LOCK;
    pyo = PySVRV_New(sv);
    SvREFCNT_dec(sv);  /* since PySVRV_New incremented it */
    PERL_UNLOCK;

    ASSERT_LOCK_PYTHON;
    return pyo;
}

static PyObject*
pysvrv_get(PySVRV *self, PyObject *args)
{
    char *key;
    Py_ssize_t keylen;
    PyObject *failobj = Py_None;
    SV** svp;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_ParseTuple(args, "s#|O:get", &key, &keylen, &failobj))
	return NULL;

    ENTER_PERL;
    SET_CUR_PERL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVHV);

    svp = hv_fetch((HV*)SvRV(self->rv), key, keylen, 0);

    ENTER_PYTHON;
    if (svp) {
	PyObject *tmp;
	PERL_LOCK;
	tmp = sv2pyo(*svp);
	PERL_UNLOCK;
	return tmp;
    }

    Py_INCREF(failobj);
    ASSERT_LOCK_PYTHON;
    return failobj;
}



/* The following function as adapted from Larry Wall's pp_splice in
 * the perl source.  If will remove len elements at offset and
 * make room for newlen new elements by filling the space with &sv_undef.
 */
static int
array_splice(AV* av, I32 offset, I32 len, I32 newlen)
{
    /* This function assumes that we hold the perl lock only, when it is
     * called.  On normal return it will not change lock status, but on
     * errors it swith to python lock mode, set execption state and return -1.
     */

    I32 asize;
    I32 diff, after, i;
    SV **src;
    SV **dst;
    dCTXP;

    ASSERT_LOCK_PERL;
    SET_CUR_PERL;
/* #define SPLICE_DEBUG  */
    asize = av_len(av) + 1;
    if (offset < 0)
	offset += asize;
    if (offset < 0 || offset > asize) {
	ENTER_PYTHON;
	PyErr_SetString(PyExc_IndexError, "perl array index out of range");
	return -1;
    }

    if (len < 0) {
	len += asize - offset;
	if (len < 0)
	    len = 0;
    }

    if (newlen < 0) {
	ENTER_PYTHON;
	PyErr_BadInternalCall();
	return -1;
    }

    after = asize - offset - len;
    if (after < 0) {
	len += after;
	after = 0;
	if (!AvALLOC(av))
	    av_extend(av, 0);
    }
    
    diff = newlen - len;
    if (newlen && !AvREAL(av) && AvREIFY(av))
	Perl_av_reify(aTHX_ av);

#ifdef SPLICE_DEBUG
    printf("splice(offset=%d, len=%d, diff=%d, after=%d, fill=%d, max=%d, pre=%d)\n",
	   offset, len, diff, after, AvFILLp(av), AvMAX(av), AvARRAY(av) - AvALLOC(av));
#endif

    /* free old stuff */
    src = &AvARRAY(av)[offset];
    for (i = len; i; i--) {
#ifdef SPLICE_DEBUG
	printf("   free #%d\n", src - AvARRAY(av));
#endif
	SvREFCNT_dec(*src);
	*src = &PL_sv_undef;
	src++;
    }
  
    if (diff < 0) {                       /* shrinking the area */
	AvFILLp(av) += diff;
	if (offset < after) {		/* easier to pull up */
	    if (offset)			/* esp. if nothing to pull */
		Move(AvARRAY(av), AvARRAY(av)-diff, offset, SV*);
	    SvPVX(av) = (char*)(AvARRAY(av) - diff);
	    AvMAX(av) += diff;
	    dst = AvARRAY(av) + diff;
	}
	else {
	    if (after) {			/* anything to pull down? */
		src = AvARRAY(av) + offset + len;
		dst = src + diff;		/* diff is negative */
		Move(src, dst, after, SV*);
	    }
	    dst = &AvARRAY(av)[AvFILLp(av)+1];
	}
	i = -diff;
    }
    else if (diff > 0) {				/* expanding */
	/* push up or down? */
	if (offset < after && diff <= AvARRAY(av) - AvALLOC(av)) {
	    if (offset) {
		src = AvARRAY(av);
		dst = src - diff;
		Move(src, dst, offset, SV*);
	    }
	    SvPVX(av) = (char*)(AvARRAY(av) - diff);  /* diff is positive */
	    AvMAX(av) += diff;
	    AvFILLp(av) += diff;
	    dst = AvARRAY(av) + offset;
	}
	else {
	    if (AvFILLp(av) + diff > AvMAX(av))	/* oh, well */
		av_extend(av, AvFILLp(av) + diff);
	    AvFILLp(av) += diff;
	    if (after) {
		src = AvARRAY(av) + offset + len;
		dst = src + diff;
		Move(src, dst, after, SV*);
		dst = src;
	    }
	    else 
		dst = AvARRAY(av) + AvFILLp(av);
	}
	i = diff;
    }

    /* clear moved away area */
    while (i) {
	dst[--i] = &PL_sv_undef;
#ifdef SPLICE_DEBUG
	printf("   clear #%d\n", dst - AvARRAY(av) + i);
#endif
    }

#ifdef SPLICE_DEBUG
    printf("   -->(fill=%d, max=%d, pre=%d)\n",
	   AvFILLp(av), AvMAX(av), AvARRAY(av) - AvALLOC(av));
#endif

    ASSERT_LOCK_PERL;
    return 0;
}


static PyObject *
array_item(AV* av, I32 index)
{
    /* Assumes python lock */
    SV** svp;
    I32 size;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    ENTER_PERL;
    SET_CUR_PERL;
    svp = av_fetch(av, index, 0);
    if (svp) {
	PyObject *tmp;
	int status = try_SvGETMAGIC(*svp);
	ENTER_PYTHON;
	if (status == -1)
	    goto FAIL;
	PERL_LOCK;
	tmp = sv2pyo(*svp);
	PERL_UNLOCK;
	ASSERT_LOCK_PYTHON;
	return tmp;
    }

    ENTER_PYTHON;
    if (PyErr_Occurred())
	goto FAIL;

    /* av_fetch also returns 0 for empty slots filled with PL_av_undef,
     * so we need to compensate for that by testing if we actually are
     * within bounds.
     */
    ENTER_PERL;
    size = try_array_len(av);

    ENTER_PYTHON;
    if (size == -1)
	goto FAIL;

    if (index < size  && index >= -size) {
	ASSERT_LOCK_PYTHON;
	return Py_BuildValue("");
    }

    PyErr_SetString(PyExc_IndexError, "perl array index out of range");
FAIL:
    ASSERT_LOCK_PYTHON;
    return NULL;
}


static PyObject*
pysvrv_append(PySVRV *self, PyObject *args)
{
    PyObject *v;
    AV* av;
    SV* sv;
    SV** svp;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_ParseTuple(args, "O:append", &v))
	return NULL;

    PERL_LOCK;
    SET_CUR_PERL;
    sv = pyo2sv(v);

    PYTHON_UNLOCK;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVAV);
    av = (AV*)SvRV(self->rv);
    svp = av_store(av, av_len(av)+1, sv);
    if (!svp) {
	SvREFCNT_dec(sv);
	ENTER_PYTHON;
	PyErr_SetString(PyExc_RuntimeError, "av_store failed");
	return NULL;
    }

    ENTER_PYTHON;
    Py_INCREF(Py_None);
    ASSERT_LOCK_PYTHON;
    return Py_None;
}

static PyObject*
pysvrv_insert(PySVRV *self, PyObject *args)
{
    int i;
    PyObject *v;
    AV* av;
    SV* sv;
    SV** svp;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_ParseTuple(args, "iO:insert", &i, &v))
	return NULL;

    ENTER_PERL;
    SET_CUR_PERL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVAV);
    av = (AV*)SvRV(self->rv);
    if (array_splice(av, i, 0, 1) == -1) {
	ASSERT_LOCK_PYTHON;
	return NULL;
    }

    ENTER_PYTHON;
    PERL_LOCK;
    sv = pyo2sv(v);
    PYTHON_UNLOCK;

    svp = av_store(av, i, sv);
    if (!svp) {
	SvREFCNT_dec(sv);
	ENTER_PYTHON;
	PyErr_SetString(PyExc_RuntimeError, "av_store failed");
	ASSERT_LOCK_PYTHON;
	return NULL;
    }

    ENTER_PYTHON;
    Py_INCREF(Py_None);
    ASSERT_LOCK_PYTHON;
    return Py_None;
}

static PyObject*
pysvrv_extend(PySVRV *self, PyObject *args)
{
    PyObject *o;
    AV* av;
    int n;
    STRLEN size, i;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;

    if (!PyArg_ParseTuple(args, "O:extend", &o))
	goto FAIL;

    if (!PySequence_Check(o)) {
	PyErr_SetString(PyExc_TypeError,
			"array.extend() argument must be a sequence");
	goto FAIL;
    }

    n = PySequence_Length(o);
    if (n < 0)
	goto FAIL;

    ENTER_PERL;
    SET_CUR_PERL;

    assert(SvTYPE(SvRV(self->rv)) == SVt_PVAV);
    av = (AV*)SvRV(self->rv);
    size = av_len(av) + 1;
    if (n)
	av_extend(av, (size-1) + n);

    /* Special case for a.extend(a) */
    if (PySVRV_Check(o) && SvRV(((PySVRV*)o)->rv) == (SV*)av) {
	SV** svp;
	for (i = 0; i < size; i++) {
	    svp = av_fetch(av, i, 0);
	    if (svp) {
		if (av_store(av, size + i, *svp))
		    SvREFCNT_inc(*svp);
	    }
	}
	ENTER_PYTHON;
	goto DONE;
    }

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

	ASSERT_LOCK_PYTHON;
	item = PySequence_GetItem(o, i);

	if (item) {
	    SV* item_sv;
	    PERL_LOCK;
	    item_sv = pyo2sv(item);
	    PYTHON_UNLOCK;
	    if (!av_store(av, size + i, item_sv)) {
		SvREFCNT_dec(item_sv);
		ENTER_PYTHON;
		PyErr_SetString(PyExc_RuntimeError, "av_store failed");
		goto FAIL;
	    }
	    ENTER_PYTHON;
	}
	else {
	    if (PyErr_ExceptionMatches(PyExc_IndexError)) {
		PyErr_Clear();
		break;
	    }
	    /* Something else bad happened */
	    goto FAIL;
	}
    }

DONE:
    ASSERT_LOCK_PYTHON;
    Py_INCREF(Py_None);
    return Py_None;

FAIL:
    /* XXX can we undo whatever we already might have stored in av??? */
    ASSERT_LOCK_PYTHON;
    return NULL;
}

static PyObject*
pysvrv_pop(PySVRV *self, PyObject *args)
{
    AV* av;
    I32 len;
    int i = -1;
    SV* sv;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_ParseTuple(args, "|i:pop", &i))
	return NULL;

    ENTER_PERL;
    SET_CUR_PERL;

    assert(SvTYPE(SvRV(self->rv)) == SVt_PVAV);
    av = (AV*)SvRV(self->rv);
    len = av_len(av);

    if (len == -1) {
	ENTER_PYTHON;
	PyErr_SetString(PyExc_IndexError, "pop from empty list");
	ASSERT_LOCK_PYTHON;
	return NULL;
    }

    if (i == -1 || i == len) {
	SV* tmp = av_pop(av);
	PyObject *o;
	ENTER_PYTHON;
	PERL_LOCK;
	o = sv2pyo(tmp);
	PERL_UNLOCK;
	ASSERT_LOCK_PYTHON;
	return o;
    }
    else {
	PyObject* pyo;
	ENTER_PYTHON;
	pyo = array_item(av, i);
	if (!pyo) {
	    ASSERT_LOCK_PYTHON;
	    return NULL;
	}
	ENTER_PERL;
	if (array_splice(av, i, 1, 0) == -1) {
	    Py_DECREF(pyo);
	    ASSERT_LOCK_PYTHON;
	    return NULL;
	}
	ENTER_PYTHON;
	return pyo;
    }
}

static int
array_index(AV* av, PyObject *v)
{
    I32 i;
    I32 len;
    SV** svp;
    dCTXP;

    ASSERT_LOCK_PERL;
    SET_CUR_PERL;

    len = av_len(av);
    for (i = 0; i <= len; i++) {
	ASSERT_LOCK_PERL;
	svp = av_fetch(av, i, 0);
	if (svp) {
	    PyObject *x;
	    int cmp;
	    ENTER_PYTHON;
	    PERL_LOCK;
	    x = sv2pyo(*svp);
	    PERL_UNLOCK;
	    cmp = PyObject_RichCompareBool(x, v, Py_EQ);
	    Py_DECREF(x);
	    if (cmp == 0) {
		ENTER_PERL;
		return i;
	    }
	    if (cmp == -1 && PyErr_Occurred()) {
		ENTER_PERL;
		return -1;
	    }
	    ENTER_PERL;
	}
	else if (v == Py_None) {
	    ASSERT_LOCK_PERL;
	    return i;
	}
    }
    ASSERT_LOCK_PERL;
    return -1;
}

static PyObject*
pysvrv_remove(PySVRV *self, PyObject *args)
{
    AV* av;
    PyObject *v;
    int index;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_ParseTuple(args, "O:index", &v))
	return NULL;

    ENTER_PERL;
    SET_CUR_PERL;

    assert(SvTYPE(SvRV(self->rv)) == SVt_PVAV);
    av = (AV*)SvRV(self->rv);

    index = array_index(av, v);

    if (index == -1) {
	ENTER_PYTHON;
	if (!PyErr_Occurred())
	    PyErr_SetString(PyExc_ValueError,
			    "perlarray.remove(x): x not in list");
	ASSERT_LOCK_PYTHON;
	return NULL;
    }

    array_splice(av, index, 1, 0);

    ENTER_PYTHON;
    Py_INCREF(Py_None);
    ASSERT_LOCK_PYTHON;
    return Py_None;
}

static PyObject*
pysvrv_index(PySVRV *self, PyObject *args)
{
    AV* av;
    PyObject *v;
    int index;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_ParseTuple(args, "O:index", &v))
	return NULL;

    ENTER_PERL;
    SET_CUR_PERL;

    assert(SvTYPE(SvRV(self->rv)) == SVt_PVAV);
    av = (AV*)SvRV(self->rv);

    index = array_index(av, v);

    ENTER_PYTHON;
    if (index == -1) {
	if (!PyErr_Occurred())
	    PyErr_SetString(PyExc_ValueError,
			    "perlarray.index(x): x not in list");
	ASSERT_LOCK_PYTHON;
	return NULL;
    }

    ASSERT_LOCK_PYTHON;
    return PyLong_FromLong((long)index);
}

static PyObject*
pysvrv_count(PySVRV *self, PyObject *args)
{
    AV* av;
    I32 len, i;
    PyObject *v;
    SV** svp;
    int count = 0;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    SET_CUR_PERL;

    if (!PyArg_ParseTuple(args, "O:count", &v))
	return NULL;

    ENTER_PERL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVAV);
    av = (AV*)SvRV(self->rv);

    len = av_len(av);
    for (i = 0; i <= len; i++) {
	ASSERT_LOCK_PERL;
	svp = av_fetch(av, i, 0);
	if (svp) {
	    PyObject *x;
	    int cmp;
	    ENTER_PYTHON;
	    PERL_LOCK;
	    x = sv2pyo(*svp);
	    PERL_UNLOCK;
	    cmp = PyObject_RichCompareBool(x, v, Py_EQ);
	    Py_DECREF(x);
	    if (cmp == 0)
		count++;
	    if (cmp == -1 && PyErr_Occurred()) {
		ASSERT_LOCK_PYTHON;
		return NULL;
	    }
	    ENTER_PERL;
	}
	else if (v == Py_None)
	    count++;
    }
    ENTER_PYTHON;
    return PyLong_FromLong((long)count);
}

static PyObject*
pysvrv_reverse(PySVRV *self, PyObject *args)
{
    AV* av;
    I32 len, i;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    if (!PyArg_Parse(args, ""))
	return NULL;

    ENTER_PERL;
    SET_CUR_PERL;

    assert(SvTYPE(SvRV(self->rv)) == SVt_PVAV);
    av = (AV*)SvRV(self->rv);

    if (SvREADONLY(av)) {
	ENTER_PYTHON;
	PyErr_SetString(PyExc_TypeError, "read only array can be modified");
	ASSERT_LOCK_PYTHON;
	return NULL;
    }

    if (SvTIED_mg((SV*)av, 'P')) {
	ENTER_PYTHON;
	PyErr_SetString(PyExc_TypeError, "tied array");
	ASSERT_LOCK_PYTHON;
	return NULL;
    }

    len = av_len(av);
  
    if (len > 0) {
	for (i = (len-1) / 2; i >= 0; i--) {
	    SV* tmp;
	    I32 other = len - i;
	    /* swap them */
	    tmp = AvARRAY(av)[i];
	    AvARRAY(av)[i] = AvARRAY(av)[other];
	    AvARRAY(av)[other] = tmp;
	}
    }

    ENTER_PYTHON;
    Py_INCREF(Py_None);
    ASSERT_LOCK_PYTHON;
    return Py_None;
}

static PyObject*
pysvrv_sort(PySVRV *self, PyObject *args)
{
    ASSERT_LOCK_PYTHON;
    PyErr_SetString(PyExc_NotImplementedError, "array sort");
    return NULL;
}

/* only useful for debugging (and test suite) */
static PyObject*
pysvrv_av_alloc(PySVRV *self, PyObject *args)
{
    AV* av;
    PyObject *t;
    long left, middle, right;

    dCTXP;

    ASSERT_LOCK_PYTHON;
    SET_CUR_PERL;
    CHECK_OWNED_PY;

    if (!PyArg_Parse(args, ""))
	return NULL;

    ENTER_PERL;
    assert(SvTYPE(SvRV(self->rv)) == SVt_PVAV);
    av = (AV*)SvRV(self->rv);

    left = AvARRAY(av) - AvALLOC(av);  /* extra allocated at beginning */
    middle = AvFILLp(av) + 1;          /* used */
    right  = AvMAX(av) - AvFILLp(av);  /* extra allocated at end */

    ENTER_PYTHON;
    t = PyTuple_New(3);
    if (t == NULL) 
	return NULL;

    PyTuple_SetItem(t, 0, PyLong_FromLong(left));
    PyTuple_SetItem(t, 1, PyLong_FromLong(middle));
    PyTuple_SetItem(t, 2, PyLong_FromLong(right));
    ASSERT_LOCK_PYTHON;
    return t;
}


static PyMethodDef pysvrv_methods[] = {
    /* mapp_methods */
    {"has_key",	(PyCFunction)pysvrv_has_key, METH_VARARGS},
    {"keys",	(PyCFunction)pysvrv_keys,    METH_NOARGS},
    {"items",	(PyCFunction)pysvrv_items,   METH_NOARGS},
    {"values",	(PyCFunction)pysvrv_values,  METH_NOARGS},
    {"update",	(PyCFunction)pysvrv_update,  METH_VARARGS},
    {"clear",	(PyCFunction)pysvrv_clear,   METH_NOARGS},
    {"copy",	(PyCFunction)pysvrv_copy,    METH_NOARGS},
    {"get",     (PyCFunction)pysvrv_get,     METH_VARARGS},
    /* list_methods */
    {"append",	(PyCFunction)pysvrv_append,  METH_VARARGS},
    {"insert",	(PyCFunction)pysvrv_insert,  METH_VARARGS},
    {"extend",  (PyCFunction)pysvrv_extend,  METH_VARARGS},
    {"pop",	(PyCFunction)pysvrv_pop,     METH_VARARGS},
    {"remove",	(PyCFunction)pysvrv_remove,  METH_VARARGS},
    {"index",	(PyCFunction)pysvrv_index,   METH_VARARGS},
    {"count",	(PyCFunction)pysvrv_count,   METH_VARARGS},
    {"reverse",	(PyCFunction)pysvrv_reverse, METH_NOARGS},
    {"sort",	(PyCFunction)pysvrv_sort,    METH_VARARGS},
    {"av_alloc",(PyCFunction)pysvrv_av_alloc,METH_NOARGS},
    {NULL, NULL} /* sentinel */
};

static PyMethodDef list_methods[] = {
    {"append",	(PyCFunction)pysvrv_append,  METH_VARARGS},
    {"insert",	(PyCFunction)pysvrv_insert,  METH_VARARGS},
    {"extend",  (PyCFunction)pysvrv_extend,  METH_VARARGS},
    {"pop",	(PyCFunction)pysvrv_pop,     METH_VARARGS},
    {"remove",	(PyCFunction)pysvrv_remove,  METH_VARARGS},
    {"index",	(PyCFunction)pysvrv_index,   METH_VARARGS},
    {"count",	(PyCFunction)pysvrv_count,   METH_VARARGS},
    {"reverse",	(PyCFunction)pysvrv_reverse, METH_NOARGS},
    {"sort",	(PyCFunction)pysvrv_sort,    METH_VARARGS},
    {"av_alloc",(PyCFunction)pysvrv_av_alloc,METH_NOARGS},
    {NULL, NULL} /* sentinel */
};


static PyObject*
pysvrv_getattro(PySVRV *self, PyObject *nameobj)
{
    PyObject *val;
    dCTXP;
    char *name = "";

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    SET_CUR_PERL;

    if (PyUnicode_Check(nameobj))
	name = PyUnicode_AsUTF8(nameobj);

    if (strcmp(name, "__wantarray__") == 0) {
	if (self->gimme == G_VOID)
	    val = Py_BuildValue(""); /* None */
	else 
	    val = PyLong_FromLong((long)(self->gimme == G_ARRAY));
	goto success;
    }
    else if (strcmp(name, "__methodname__") == 0) {
	if (self->methodname)
	    val = PyUnicode_FromString(self->methodname);
	else
	    val = Py_BuildValue(""); /* None */
	goto success;
    }
    else if (strcmp(name, "__class__") == 0) {
	SV *sv;
	ENTER_PERL;
	sv =  SvRV(self->rv);
	if (SvOBJECT(sv)) {
	    char *klass = HvNAME(SvSTASH(sv));
	    ENTER_PYTHON;
	    val = PyUnicode_FromString(klass);
	}
	else {
	    ENTER_PYTHON;
	    val = Py_BuildValue("");
	}
	goto success;
    }
    else if (strcmp(name, "__type__") == 0) {
	const char *tmp;
	ENTER_PERL;
	tmp = sv_reftype(SvRV(self->rv), 0);
	ENTER_PYTHON;
	val = PyUnicode_FromString(tmp);
	goto success;
    }
    else if (strcmp(name, "__value__") == 0) {
	SV *sv = SvRV(self->rv);
	switch (SvTYPE(sv)) {
	case SVt_PVAV:
	case SVt_PVHV:
	case SVt_PVCV:
	    PyErr_SetString(PyExc_AttributeError, name);
	    goto error;
	    break;
	default:
	    PERL_LOCK;
	    val = sv2pyo(sv); 
	    PERL_UNLOCK;
	}
	goto success;
    }
    else if (strcmp(name, "__readonly__") == 0) {
	val = PyLong_FromLong(SvREADONLY(SvRV(self->rv)) != 0);
	goto success;
    }
    else if (self->methodname) {
	PyErr_SetString(PyExc_AttributeError, name);
	goto error;
    }
    else if (SvOBJECT(SvRV(self->rv))) {
	PySVRV *method_obj;
	int len;
	PERL_LOCK;
	method_obj = (PySVRV *)PySVRV_New(self->rv);
	len = strlen(name);

	New(999, method_obj->methodname, len+1, char);
	Copy(name, method_obj->methodname, len+1, char);

	if (len > 6 && strEQ(name+len-6, "_tuple")) {
	    method_obj->methodname[len-6] = '\0';
	    method_obj->gimme  = G_ARRAY;
	}
	else {
	    method_obj->gimme  = self->gimme;
	}
	PERL_UNLOCK;
	val = (PyObject *)method_obj;
	goto success;
    }
    else if (SvTYPE(SvRV(self->rv)) == SVt_PVAV || SvTYPE(SvRV(self->rv)) == SVt_PVHV) {
	val = PyObject_GenericGetAttr((PyObject *)self, nameobj);
	goto success;
    }
    else if (!(val = PyObject_GenericGetAttr((PyObject *)self, nameobj))) {
	if (!PyErr_ExceptionMatches(PyExc_AttributeError))
	    goto error;
	else
	    goto success;
    }
    else {
	PyErr_SetString(PyExc_AttributeError, name);
	goto error;
    }

error:
    val = NULL;
success:
    ASSERT_LOCK_PYTHON;
    return val;
}

static int
pysvrv_setattro(PySVRV *self, PyObject *nameobj, PyObject *val)
{
    int status;
    dCTXP;
    char *name = "";

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_INT;
    SET_CUR_PERL;

    if (PyUnicode_Check(nameobj))
	name = PyUnicode_AsUTF8(nameobj);

    if (strcmp(name, "__wantarray__") == 0) {
	if (val == Py_None)
	    self->gimme = G_VOID;
	else
	    self->gimme = PyObject_IsTrue(val) ? G_ARRAY : G_SCALAR;
	status = 0;
    }
    else if (strcmp(name, "__methodname__") == 0) {
	if (PyUnicode_Check(val)) {
	    PERL_LOCK;
	    Safefree(self->methodname);
	    New(998, self->methodname, PyUnicode_GetLength(val)+1, char);
	    Copy(PyUnicode_AsUTF8(val), self->methodname,
		 PyUnicode_GetLength(val)+1, char);
	    PERL_UNLOCK;
	    status = 0;
	}
	else {
	    PyErr_SetString(PyExc_TypeError, "__methodname__ must be string");
	    status = -1;
	}
    }
    else if (strcmp(name, "__class__") == 0) {
	if (PyUnicode_Check(val)) {
	    char *klass = PyUnicode_AsUTF8(val);
	    ENTER_PERL;
	    sv_bless(self->rv, gv_stashpv(klass, 1));
	    ENTER_PYTHON;
	    status = 0;
	}
	else if (val == Py_None) {
	    /* unbless */
	    PyErr_SetString(PyExc_NotImplementedError, "unbless");
	    status = -1;
	}
	else {
	    PyErr_SetString(PyExc_TypeError, "__class__ must be string");
	    status = -1;
	}
    }
    else if (strcmp(name, "__value__") == 0) {
	SV *sv;
	SV *val_sv;
	PERL_LOCK;
	sv = SvRV(self->rv);
	switch (SvTYPE(sv)) {
	case SVt_PVAV:
	case SVt_PVHV:
	case SVt_PVCV:
	    PERL_UNLOCK;
	    PyErr_SetString(PyExc_AttributeError, name);
	    status = -1;
	    break;
	default:
	    val_sv = pyo2sv(val);
	    SvSetMagicSV(sv, val_sv);
	    SvREFCNT_dec(val_sv);
	    PERL_UNLOCK;
	    status = 0;
	}
    }
    else if (strcmp(name, "__readonly__") == 0) {
	/* to give write access to this attribute is not really a good idea,
	 * but it can be fun for experimentation.
	 */
	if (PyObject_IsTrue(val))
	    SvREADONLY_on(SvRV(self->rv));
	else
	    SvREADONLY_off(SvRV(self->rv));
	status = 0;
    }
    else {
	PyErr_SetString(PyExc_AttributeError, name);
	status = -1;
    }

    ASSERT_LOCK_PYTHON;
    return status;
}


static PyObject*
pysvrv_call(PySVRV *self, PyObject *arg, PyObject *kw)
{
    dCTX;
    PyObject *res;
    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    res = call_perl(self->methodname, self->rv, self->gimme, arg, kw);
    ASSERT_LOCK_PYTHON;
    return res;
}


static PyObject*
pysvrv_repr(PySVRV *self)
{
    SV* tmp_sv;
    SV* sv;
    PyObject* o;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    /* We don't CHECK_OWNED here and hope for the best :-) */
    ENTER_PERL;
    SET_CUR_PERL;

    tmp_sv = newSVpvn("<", 1);
    sv = SvRV(self->rv);
    if (self->methodname) {
	sv_catpvf(tmp_sv, "method %s of ", self->methodname);
    }

    sv_catpvn(tmp_sv, "perl ", 5);
    if (SvOBJECT(sv)) {
	sv_catpvf(tmp_sv, "%s=", HvNAME(SvSTASH(sv)));
    }
    sv_catpvf(tmp_sv, "%s(0x%p) ref at %p",
	      sv_reftype(sv, 0), sv, self);

#if 0
    sv_catpvf(tmp_sv, " (%s)", self->gimme == G_VOID   ? "G_VOID" :
	      self->gimme == G_SCALAR ? "G_SCALAR" :
	      self->gimme == G_ARRAY  ? "G_ARRAY"  : "?");
#endif
  
    sv_catpvn(tmp_sv, ">", 1);
    ENTER_PYTHON;

    o = PyUnicode_FromStringAndSize(SvPVX(tmp_sv), SvCUR(tmp_sv));
    SvREFCNT_dec(tmp_sv);

    ASSERT_LOCK_PYTHON;
    return o;
}


static void
type_error(char *msg, SV* sv)
{
    SV* tmp;
    dCTXP;

    ASSERT_LOCK_PYTHON;

    ENTER_PERL;
    SET_CUR_PERL;

    tmp = newSVpvf("%s perl %s", msg, sv_reftype(sv, 0));

    ENTER_PYTHON;
    PyErr_SetString(PyExc_TypeError, SvPVX(tmp));

    PERL_LOCK;
    SvREFCNT_dec(tmp);
    PERL_UNLOCK;

    ASSERT_LOCK_PYTHON;
}


static Py_ssize_t
pysvrv_length(PySVRV *self)
{
    SV* sv;
    Py_ssize_t len;
    dCTX;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_INT;

    ENTER_PERL;
    sv = SvRV(self->rv);
    if (SvTYPE(sv) == SVt_PVAV) {
	len = try_array_len((AV*)sv);
    }
    else if (SvTYPE(sv) == SVt_PVHV) {
	len = HvKEYS(sv); /* XXX support tied hashes */
    }
    else {
	ENTER_PYTHON;
	type_error("Can't count", sv);
	len = -1;
	ENTER_PERL;  /* just so we can change back :-( */
    }
    ENTER_PYTHON;

    ASSERT_LOCK_PYTHON;
    return len;
}

static int
pysvrv_bool(PySVRV *self)
{
    SV* sv;
    int v;
    dCTX;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_INT;

    ENTER_PERL;
    sv = SvRV(self->rv);
    if (SvTYPE(sv) == SVt_PVAV) {
	v = try_array_len((AV*)sv) != 0;
    }
    else if (SvTYPE(sv) == SVt_PVHV) {
	v = HvKEYS(sv) != 0; /* XXX support tied hashes */
    }
    else {
	v = 1;
    }
    ENTER_PYTHON;

    ASSERT_LOCK_PYTHON;
    return v;
}


static PyObject *
pysvrv_item(PySVRV *self, Py_ssize_t index)
{
    SV* sv;
    PyObject *item;
    dCTX;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;

    sv = SvRV(self->rv);
    if (SvTYPE(sv) == SVt_PVAV) {
	item = array_item((AV*)sv, index);
    }
    else {
	type_error("Can't sequence index", sv);
	item = NULL;
    }

    ASSERT_LOCK_PYTHON;
    return item;
}


static PyObject*
pysvrv_subscript(PySVRV *self, PyObject *key)
{
    SV* sv;
    PyObject *val = key;  /* just something different than null */
    dCTXP;

    ASSERT_LOCK_PYTHON;
    SET_CUR_PERL;
    CHECK_OWNED_PY;
    assert(key);

    sv = SvRV(self->rv);
    if (SvTYPE(sv) == SVt_PVAV) {
	I32 index;
	if (PyLong_Check(key))
	    index = PyLong_AsLong(key);
	else if (PyLong_Check(key)) {
	    index = PyLong_AsLong(key);
	    if (index == -1 && PyErr_Occurred())
		val = NULL;
	}
	else {
	    PyErr_SetString(PyExc_TypeError, "perl array index must be integer");
	    val = NULL;
	}
	if (val)
	    val = array_item((AV*)sv, index); 
    }
    else if (SvTYPE(sv) == SVt_PVHV) {
	HV* hv = (HV*)sv;
	if (PyUnicode_Check(key)) {
	    SV** svp;
	    ENTER_PERL;
	    svp = hv_fetch(hv, PyUnicode_AsUTF8(key), PyUnicode_GetLength(key), 0);
	    if (svp) {
		SvGETMAGIC(*svp);
		PYTHON_LOCK;
		val = sv2pyo(*svp);
		PERL_UNLOCK;
	    }
	    else {
		ENTER_PYTHON;
		PyErr_SetObject(PyExc_KeyError, key);
		val = NULL;
	    }
	}
	else {
	    PyErr_SetString(PyExc_TypeError, "perl hash key must be string");
	    val = NULL;
	}
    }
    else {
	type_error("Can't index", sv);
	val = NULL;
    }

    assert(val != key);
    ASSERT_LOCK_PYTHON;
    return val;
}


static int
pysvrv_ass_sub(PySVRV *self, PyObject *key, PyObject *val)
{
    SV* sv;
    int status;  /* return value */
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_INT;
    SET_CUR_PERL;

    sv = SvRV(self->rv);
    if (SvTYPE(sv) == SVt_PVAV) {
	AV* av = (AV*)sv;
	I32 len;
	I32 index;
	SV* val_sv;
	SV** svp;

	if (PyLong_Check(key))
	    index = PyLong_AsLong(key);
	else if (PyLong_Check(key)) {
	    index = PyLong_AsLong(key);
	    if (index == -1 && PyErr_Occurred())
		goto FAIL;
	}
	else {
	    PyErr_SetString(PyExc_TypeError, "perl array index must be integer");
	    goto FAIL;
	}

	ENTER_PERL;
	if (!val) {
	    /* delete */
	    status = array_splice(av, index, 1, 0);
	    if (status == -1)
		ENTER_PERL;  /* Blææ!! */
	}
	else {
	    len = av_len(av);

	    ENTER_PYTHON;
	    if (index < (-len-1) || index > len) {
		PyErr_SetString(PyExc_IndexError, "perl array assignment index out of range");
		goto FAIL;
	    }

	    PERL_LOCK;
	    val_sv = pyo2sv(val);

	    PYTHON_UNLOCK;
	    svp = av_store(av, index, val_sv);
	    if (!svp) {
		SvREFCNT_dec(val_sv);
		ENTER_PYTHON;
		PyErr_SetString(PyExc_RuntimeError, "av_store failed");
		goto FAIL;
	    }
	    status = 0;
	}
	ENTER_PYTHON;
    }
    else if (SvTYPE(sv) == SVt_PVHV) {
	HV* hv = (HV*)sv;
	if (PyUnicode_Check(key)) {
	    char *key_str = PyUnicode_AsUTF8(key);
	    int   key_len = PyUnicode_GetLength(key);
	    if (val) {
		SV* val_sv;
		SV** svp;

		PERL_LOCK;
		val_sv = pyo2sv(val);
	
		PYTHON_UNLOCK;
		svp = hv_store(hv, key_str, key_len, val_sv, 0);
		if (svp) {
		    if (try_SvSETMAGIC(*svp) == -1) {
			ENTER_PYTHON;
			goto FAIL;
		    }
                }
		ENTER_PYTHON;
		if (!svp) {
		    SvREFCNT_dec(val_sv);
		    PyErr_SetString(PyExc_RuntimeError, "av_store failed");
		    goto FAIL;
		}
	    }
	    else {
		SV* sv;
		int key_deleted;

		ENTER_PERL;
		/* Since hv_delete gives us a mortal copy, we set up a block
		 * to get rid of it.
		 */
		ENTER;
		SAVETMPS;

		sv = hv_delete(hv, key_str, key_len, 0);
		key_deleted = (sv != NULL);

		FREETMPS;  /* sv invalidated */
		LEAVE;

		ENTER_PYTHON;
		if (!key_deleted) {
		    PyErr_SetObject(PyExc_KeyError, key);
		    goto FAIL;
		}
	    }
	    status = 0;
	}
	else {
	    PyErr_SetString(PyExc_TypeError, "perl hash key must be string");
	    status = -1;
	}
    }
    else {
	type_error("Can't index", sv);
    FAIL:
	status = -1;
    }

    ASSERT_LOCK_PYTHON;
    return status;
}


static PyObject *
pysvrv_concat(PySVRV *self, PyObject *other)
{
    SV* sv1;
    PyObject *pyo_res;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    SET_CUR_PERL;

    sv1 = SvRV(self->rv);
    if (SvTYPE(sv1) == SVt_PVAV) {
	if (other && PySVRV_Check(other)) {
	    SV* sv2 = SvRV(((PySVRV*)other)->rv);
#ifdef MULTI_PERL
	    if (!owned_by((PySVRV*)other, ctx->perl)) {
		pyo_res = NULL;
		goto DONE;
	    }
#endif
	    if (SvTYPE(sv2) == SVt_PVAV) {
		AV* av1 = (AV*)sv1;
		AV* av2 = (AV*)sv2;
		AV* res;
		I32 i, len1, len2;
		SV** svp;
		SV* sv;

		ENTER_PERL;
		res = newAV();
		len1 = av_len(av1) + 1;
		len2 = av_len(av2) + 1;

		av_extend(res, len1 + len2 - 1);

		for (i = 0; i < len1; i++) {
		    svp = av_fetch(av1, i, 0);
		    if (svp) {
			sv = newSVsv(*svp);
			if (!av_store(res, i, sv))
			    SvREFCNT_dec(sv);
		    }
		}

		for (i = 0; i < len2; i++) {
		    svp = av_fetch(av2, i, 0);
		    if (svp) {
			sv = newSVsv(*svp);
			if (!av_store(res, i+len1, sv))
			    SvREFCNT_dec(sv);
		    }
		}

		sv = newRV_noinc((SV*)res);
		ENTER_PYTHON;
		PERL_LOCK;
		pyo_res = PySVRV_New(sv);
		SvREFCNT_dec(sv);  /* since PySVRV_New incremented it */
		PERL_UNLOCK;
		goto DONE;
	    }
	}
	PyErr_SetString(PyExc_TypeError,
			"illegal argument type for perl array concatenation");
	pyo_res = NULL;
    }
    else {
	type_error("Can't concat", sv1);
	pyo_res = NULL;
    }

DONE:
    ASSERT_LOCK_PYTHON;
    return pyo_res;
}


static PyObject *
pysvrv_repeat(PySVRV *self, Py_ssize_t n)
{
    SV* sv;
    PyObject *pyo_res;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    ENTER_PERL;
    SET_CUR_PERL;

    sv = SvRV(self->rv);
    if (SvTYPE(sv) == SVt_PVAV) {
	AV* av = (AV*)sv;
	I32 size = av_len(av)+1;
	AV* res;
	I32 res_size;
	I32 i, j;
	SV** svp;
	SV* sv;

	if (n < 0)
	    n = 0;
    
	if (size == 0 || n == 0) {
	    res = newAV();
	}
	else {
	    res_size = size * n;
	    if (res_size / size != n) {/* check for overflow */
		ENTER_PYTHON;
		return PyErr_NoMemory();
	    }

	    res = newAV();
	    av_extend(res, res_size-1);

	    for (i = 0; i < size; i++) {
		SV** svp = av_fetch(av, i, 0);
		if (svp) {
		    for (j = 0; j < n; j++) {
			sv = newSVsv(*svp);
			if (!av_store(res, i + j*size, sv))
			    SvREFCNT_dec(sv);
		    }
		}
	    }
	}
	sv = newRV_noinc((SV*)res);
	ENTER_PYTHON;
	PERL_LOCK;
	pyo_res = PySVRV_New(sv);
	SvREFCNT_dec(sv);  /* since PySVRV_New incremented it */
	PERL_UNLOCK;
    }
    else {
	ENTER_PYTHON;
	type_error("Can't repeat", sv);
	pyo_res = NULL;
    }

    ASSERT_LOCK_PYTHON;
    return pyo_res;
}


static PyObject *
pysvrv_slice(PySVRV *self, Py_ssize_t ilow, Py_ssize_t ihigh)
{
    SV* sv;
    PyObject *pyo_res;
    dCTXP;

    ASSERT_LOCK_PYTHON;
    CHECK_OWNED_PY;
    ENTER_PERL;
    SET_CUR_PERL;

    sv = SvRV(self->rv);
    if (SvTYPE(sv) == SVt_PVAV) {
	AV* av = (AV*)sv;
	I32 size = av_len(av)+1;
	AV* res;
	I32 i;
	SV** svp;
	SV* sv;

	if (ilow < 0)
	    ilow = 0;
	if (ihigh > size)
	    ihigh = size;
	if (ihigh < ilow)
	    ihigh = ilow;

	res = newAV();
	if (ihigh != ilow)
	    av_extend(av, ihigh - ilow - 1);

	for (i = ilow; i < ihigh; i++) {
	    svp = av_fetch(av, i, 0);
	    if (svp) {
		sv = newSVsv(*svp);
		if (!av_store(res, i-ilow, sv))
		    SvREFCNT_dec(sv);
	    }
	    else if (i == ihigh - 1) {
		/* in order to get the perl array to get the right length
		 * we need to to special case the last element.
		 */
		sv = newSV(0);
		if (!av_store(res, i-ilow, sv))
		    SvREFCNT_dec(sv);
	    }
	}

	sv = newRV_noinc((SV*)res);
	ENTER_PYTHON;
	PERL_LOCK;
	pyo_res = PySVRV_New(sv);
	SvREFCNT_dec(sv);  /* since PySVRV_New incremented it */
	PERL_UNLOCK;
    }
    else {
	ENTER_PYTHON;
	type_error("Can't slice", sv);
	pyo_res = NULL;
    }

    ASSERT_LOCK_PYTHON;
    return pyo_res;
}

static int
pysvrv_ass_slice(PySVRV *self, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v)
{
    SV* sv;
    int status;  /* return value */
    dCTXP;

    CHECK_OWNED_INT;
    ASSERT_LOCK_PYTHON;
    ENTER_PERL;
    SET_CUR_PERL;

    sv = SvRV(self->rv);
    if (SvTYPE(sv) == SVt_PVAV) {
	AV* av = (AV*)sv;
	I32 size = av_len(av)+1;
	int n;
	AV* av2;
	SV** svp;

	if (v == NULL)
	    n = 0;
	else if (PySVRV_Check(v) && SvTYPE(SvRV(((PySVRV *)v)->rv)) == SVt_PVAV) {
#ifdef MULTI_PERL
	    if (!owned_by((PySVRV*)v, ctx->perl)) {
		ENTER_PYTHON;
		goto FAIL;
	    }
#endif
	    av2 = (AV*)SvRV(((PySVRV *)v)->rv);
	    n = av_len(av2)+1;
	}
	else {
	    ENTER_PYTHON;
	    PyErr_SetString(PyExc_TypeError, "Slice assignment type mismatch");
	    goto FAIL;
	}

	if (ilow < 0)
	    ilow = 0;
	if (ihigh > size)
	    ihigh = size;
	if (ihigh < ilow)
	    ihigh = ilow;

	/* printf("slice assign(%d:%d, %d)\n", ilow, ihigh, n); */

	if (array_splice(av, ilow, ihigh-ilow, n) == -1)
	    goto FAIL;
    
	/* Copy elements from av2 */
	while (n) {
	    n--;
	    svp = av_fetch(av2, n, 0);
	    if (svp) {
		SV* sv = newSVsv(*svp);
		if (!av_store(av, ilow+n, sv)) {
		    /* XXX might be to late to throw an exception :-( */
		    SvREFCNT_dec(sv);
		}
	    }
	}
	ENTER_PYTHON;
	status = 0;
    }
    else {
	ENTER_PYTHON;
	type_error("Can't slice", sv);
    FAIL:
	status = -1;
    }

    ASSERT_LOCK_PYTHON;
    return status;
}

static PyNumberMethods pysvrv_as_number = {
	0,	/*nb_add*/
	0,	/*nb_subtract*/
	0,	/*nb_multiply*/
#if PY_MAJOR_VERSION < 3
	0, 	/*nb_divide*/
#endif
	0,	/*nb_remainder*/
	0,	/*nb_divmod*/
	0,	/*nb_power*/
	0,	/*nb_negative*/
	0,	/*nb_positive*/
	0,	/*nb_absolute*/
	(inquiry)pysvrv_bool,	/*nb_bool*/
	0,	/*nb_invert*/
	0,	/*nb_lshift*/
	0,	/*nb_rshift*/
	0,	/*nb_and*/
	0,	/*nb_xor*/
	0,	/*nb_or*/
	0,	/*nb_coerce*/
	0,	/*nb_int*/
	0,	/*nb_long*/
	0,	/*nb_float*/
	0,	/*nb_oct*/
	0, 	/*nb_hex*/
};

static PyMappingMethods pysvrv_as_mapping = {
    (lenfunc)pysvrv_length, /* mp_length */
    (binaryfunc)pysvrv_subscript, /* mp_subscript */
    (objobjargproc)pysvrv_ass_sub, /* mp_ass_subscript */
};

static PySequenceMethods pysvrv_as_sequence = {
#if PY_MAJOR_VERSION >= 3
    .sq_length = (lenfunc)pysvrv_length,
    .sq_concat = (binaryfunc)pysvrv_concat,
    .sq_repeat = (ssizeargfunc)pysvrv_repeat,
    .sq_item = (ssizeargfunc)pysvrv_item,
    .sq_contains = (objobjproc)pysvrv_contains,
#else
    (lenfunc)pysvrv_length, /*sq_length*/
    (binaryfunc)pysvrv_concat, /*sq_concat*/
    (ssizeargfunc)pysvrv_repeat, /*sq_repeat*/
    (ssizeargfunc)pysvrv_item, /*sq_item*/
    (ssizessizeargfunc)pysvrv_slice, /*sq_slice*/
    0, /*sq_ass_item*/
    (ssizessizeobjargproc)pysvrv_ass_slice, /*sq_ass_slice*/
    0, /*sq_contains*/
#endif
};


//XXX must compile as a C++ file on Windows
PyTypeObject SVRVtype = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    .tp_name = "perl ref",
    .tp_basicsize = sizeof(PyTypeObject),
    .tp_dealloc = (destructor)pysvrv_dealloc,
    .tp_repr = (reprfunc)pysvrv_repr,
    .tp_as_number = &pysvrv_as_number,
    .tp_as_sequence = &pysvrv_as_sequence,
    .tp_as_mapping = &pysvrv_as_mapping,
    .tp_call = (ternaryfunc)pysvrv_call,
    .tp_getattro = (getattrofunc)pysvrv_getattro,
    .tp_setattro = (setattrofunc)pysvrv_setattro,
    .tp_methods = pysvrv_methods,
};

#ifdef __cplusplus
}
#endif

