/* Cell object implementation */

#include "Python.h"
#include "internal/mem.h"
#include "internal/pystate.h"

PyObject *
PyCell_New(PyObject *obj)
{
    PyCellObject *op;

    op = (PyCellObject *)PyObject_GC_New(PyCellObject, &PyCell_Type);
    if (op == NULL)
        return NULL;
    op->ob_ref = obj;
    Py_XINCREF(obj);

    _PyObject_GC_TRACK(op);
    return (PyObject *)op;
}

PyObject *
PyCell_Get(PyObject *op)
{
    if (!PyCell_Check(op)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    Py_XINCREF(((PyCellObject*)op)->ob_ref);
    return PyCell_GET(op);
}

int
PyCell_Set(PyObject *op, PyObject *obj)
{
    PyObject* oldobj;
    if (!PyCell_Check(op)) {
        PyErr_BadInternalCall();
        return -1;
    }
    oldobj = PyCell_GET(op);
    Py_XINCREF(obj);
    PyCell_SET(op, obj);
    Py_XDECREF(oldobj);
    return 0;
}

static void
cell_dealloc(PyCellObject *op)
{
    _PyObject_GC_UNTRACK(op);
    Py_XDECREF(op->ob_ref);
    PyObject_GC_Del(op);
}

static PyObject *
cell_richcompare(PyObject *a, PyObject *b, int op)
{
    /* neither argument should be NULL, unless something's gone wrong */
    assert(a != NULL && b != NULL);

    /* both arguments should be instances of PyCellObject */
    if (!PyCell_Check(a) || !PyCell_Check(b)) {
        Py_RETURN_NOTIMPLEMENTED;
    }

    /* compare cells by contents; empty cells come before anything else */
    a = ((PyCellObject *)a)->ob_ref;
    b = ((PyCellObject *)b)->ob_ref;
    if (a != NULL && b != NULL)
        return PyObject_RichCompare(a, b, op);

    Py_RETURN_RICHCOMPARE(b == NULL, a == NULL, op);
}

static PyObject *
cell_repr(PyCellObject *op)
{
    if (op->ob_ref == NULL)
        return PyUnicode_FromFormat("<cell at %p: empty>", op);

    return PyUnicode_FromFormat("<cell at %p: %.80s object at %p>",
                               op, op->ob_ref->ob_type->tp_name,
                               op->ob_ref);
}

static int
cell_traverse(PyCellObject *op, visitproc visit, void *arg)
{
    Py_VISIT(op->ob_ref);
    return 0;
}

static int
cell_clear(PyCellObject *op)
{
    Py_CLEAR(op->ob_ref);
    return 0;
}

static PyObject *
cell_get_contents(PyCellObject *op, void *closure)
{
    if (op->ob_ref == NULL)
    {
        PyErr_SetString(PyExc_ValueError, "Cell is empty");
        return NULL;
    }
    Py_INCREF(op->ob_ref);
    return op->ob_ref;
}

static int
cell_set_contents(PyCellObject *op, PyObject *obj, void *Py_UNUSED(ignored))
{
    Py_XINCREF(obj);
    Py_XSETREF(op->ob_ref, obj);
    return 0;
}

static PyGetSetDef cell_getsetlist[] = {
    {"cell_contents", (getter)cell_get_contents,
                      (setter)cell_set_contents, NULL},
    {NULL} /* sentinel */
};

PyTypeObject PyCell_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "cell",
    sizeof(PyCellObject),
    0,
    (destructor)cell_dealloc,                   /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (reprfunc)cell_repr,                        /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,    /* tp_flags */
    0,                                          /* tp_doc */
    (traverseproc)cell_traverse,                /* tp_traverse */
    (inquiry)cell_clear,                        /* tp_clear */
    cell_richcompare,                           /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    0,                                          /* tp_methods */
    0,                                          /* tp_members */
    cell_getsetlist,                            /* tp_getset */
};
