// This is the implementation of pyqtProperty.
//
// Copyright (c) 2014 Riverbank Computing Limited <info@riverbankcomputing.com>
// 
// This file is part of PyQt.
// 
// This file may be used under the terms of the GNU General Public
// License versions 2.0 or 3.0 as published by the Free Software
// Foundation and appearing in the files LICENSE.GPL2 and LICENSE.GPL3
// included in the packaging of this file.  Alternatively you may (at
// your option) use any later version of the GNU General Public
// License if such license has been publicly approved by Riverbank
// Computing Limited (or its successors, if any) and the KDE Free Qt
// Foundation. In addition, as a special exception, Riverbank gives you
// certain additional rights. These rights are described in the Riverbank
// GPL Exception version 1.1, which can be found in the file
// GPL_EXCEPTION.txt in this package.
// 
// If you are unsure which license is appropriate for your use, please
// contact the sales department at sales@riverbankcomputing.com.
// 
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.


#include <Python.h>
#include <structmember.h>

#include "qpycore_chimera.h"
#include "qpycore_pyqtproperty.h"
#include "qpycore_pyqtsignal.h"


// Forward declarations.
extern "C" {
static PyObject *pyqtProperty_getter(PyObject *self, PyObject *getter);
static PyObject *pyqtProperty_setter(PyObject *self, PyObject *setter);
static PyObject *pyqtProperty_deleter(PyObject *self, PyObject *deleter);
static PyObject *pyqtProperty_reset(PyObject *self, PyObject *reset);
static PyObject *pyqtProperty_descr_get(PyObject *self, PyObject *obj,
        PyObject *);
static int pyqtProperty_descr_set(PyObject *self, PyObject *obj,
        PyObject *value);
static PyObject *pyqtProperty_call(PyObject *self, PyObject *args,
        PyObject *kwds);
static void pyqtProperty_dealloc(PyObject *self);
static int pyqtProperty_init(PyObject *self, PyObject *args, PyObject *kwds);
static int pyqtProperty_traverse(PyObject *self, visitproc visit, void *arg);
}

static qpycore_pyqtProperty *clone(qpycore_pyqtProperty *orig);


// Doc-strings.
PyDoc_STRVAR(pyqtProperty_doc,
"pyqtProperty(type, fget=None, fset=None, freset=None, fdel=None, doc=None,\n"
"        designable=True, scriptable=True, stored=True, user=False,\n"
"        constant=False, final=False, notify=None) -> property attribute\n"
"\n"
"type is the type of the property.  It is either a type object or a string\n"
"that is the name of a C++ type.\n"
"freset is a function for resetting an attribute to its default value.\n"
"designable sets the DESIGNABLE flag (the default is True for writable\n"
"properties and False otherwise).\n"
"scriptable sets the SCRIPTABLE flag.\n"
"stored sets the STORED flag.\n"
"user sets the USER flag.\n"
"constant sets the CONSTANT flag.\n"
"final sets the FINAL flag.\n"
"notify is the NOTIFY signal.\n"
"The other parameters are the same as those required by the standard Python\n"
"property type.  Properties defined using pyqtProperty behave as both Python\n"
"and Qt properties."
"\n"
"Decorators can be used to define new properties or to modify existing ones.");

PyDoc_STRVAR(getter_doc, "Descriptor to change the getter on a property.");
PyDoc_STRVAR(setter_doc, "Descriptor to change the setter on a property.");
PyDoc_STRVAR(deleter_doc, "Descriptor to change the deleter on a property.");
PyDoc_STRVAR(reset_doc, "Descriptor to change the reset on a property.");


// Define the attributes.
static PyMemberDef pyqtProperty_members[] = {
    {const_cast<char *>("type"), T_OBJECT,
            offsetof(qpycore_pyqtProperty, pyqtprop_type), READONLY, 0},
    {const_cast<char *>("fget"), T_OBJECT,
            offsetof(qpycore_pyqtProperty, pyqtprop_get), READONLY, 0},
    {const_cast<char *>("fset"), T_OBJECT,
            offsetof(qpycore_pyqtProperty, pyqtprop_set), READONLY, 0},
    {const_cast<char *>("fdel"), T_OBJECT,
            offsetof(qpycore_pyqtProperty, pyqtprop_del), READONLY, 0},
    {const_cast<char *>("freset"), T_OBJECT,
            offsetof(qpycore_pyqtProperty, pyqtprop_reset), READONLY, 0},
    {const_cast<char *>("__doc__"), T_OBJECT,
            offsetof(qpycore_pyqtProperty, pyqtprop_doc), READONLY, 0},
    {0, 0, 0, 0, 0}
};


// Define the methods.
static PyMethodDef pyqtProperty_methods[] = {
    {"getter", pyqtProperty_getter, METH_O, getter_doc},
    {"read", pyqtProperty_getter, METH_O, getter_doc},
    {"setter", pyqtProperty_setter, METH_O, setter_doc},
    {"write", pyqtProperty_setter, METH_O, setter_doc},
    {"deleter", pyqtProperty_deleter, METH_O, deleter_doc},
    {"reset", pyqtProperty_reset, METH_O, reset_doc},
    {0, 0, 0, 0}
};


// This implements the PyQt version of the standard Python property type.
PyTypeObject qpycore_pyqtProperty_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    SIP_TPNAME_CAST("PyQt4.QtCore.pyqtProperty"),
    sizeof (qpycore_pyqtProperty),
    0,
    pyqtProperty_dealloc,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    pyqtProperty_call,
    0,
    PyObject_GenericGetAttr,
    0,
    0,
    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC|Py_TPFLAGS_BASETYPE,
    pyqtProperty_doc,
    pyqtProperty_traverse,
    0,
    0,
    0,
    0,
    0,
    pyqtProperty_methods,
    pyqtProperty_members,
    0,
    0,
    0,
    pyqtProperty_descr_get,
    pyqtProperty_descr_set,
    0,
    pyqtProperty_init,
    PyType_GenericAlloc,
    PyType_GenericNew,
    PyObject_GC_Del,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
#if PY_VERSION_HEX >= 0x02060000
    0,
#endif
#if PY_VERSION_HEX >= 0x03040000
    0,
#endif
};


// This is the sequence number to allocate to the next PyQt property to be
// defined.  This ensures that properties appear in the QMetaObject in the same
// order that they are defined in Python.
static uint pyqtprop_sequence_nr = 0;


// The pyqtProperty dealloc function.
static void pyqtProperty_dealloc(PyObject *self)
{
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;

    PyObject_GC_UnTrack(self);

    Py_XDECREF(pp->pyqtprop_get);
    Py_XDECREF(pp->pyqtprop_set);
    Py_XDECREF(pp->pyqtprop_del);
    Py_XDECREF(pp->pyqtprop_doc);
    Py_XDECREF(pp->pyqtprop_reset);
    Py_XDECREF(pp->pyqtprop_notify);
    Py_XDECREF(pp->pyqtprop_type);

    delete pp->pyqtprop_parsed_type;

    Py_TYPE(self)->tp_free(self);
}


// The descriptor getter.
static PyObject *pyqtProperty_descr_get(PyObject *self, PyObject *obj,
        PyObject *)
{
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;

    if (!obj || obj == Py_None)
    {
        Py_INCREF(self);
        return self;
    }

    if (!pp->pyqtprop_get)
    {
        PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
        return 0;
    }

    return PyObject_CallFunction(pp->pyqtprop_get, const_cast<char *>("(O)"),
            obj);
}


// The descriptor setter.
static int pyqtProperty_descr_set(PyObject *self, PyObject *obj,
        PyObject *value)
{
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
    PyObject *func, *res;

    func = (value ? pp->pyqtprop_set : pp->pyqtprop_del);

    if (!func)
    {
        PyErr_SetString(PyExc_AttributeError,
                (value ? "can't set attribute" : "can't delete attribute"));
        return -1;
    }

    if (value)
        res = PyObject_CallFunction(func, const_cast<char *>("(OO)"), obj,
                value);
    else
        res = PyObject_CallFunction(func, const_cast<char *>("(O)"), obj);

    if (!res)
        return -1;

    Py_DECREF(res);
    return 0;
}


// The pyqtProperty traverse function.
static int pyqtProperty_traverse(PyObject *self, visitproc visit, void *arg)
{
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;
    int vret;

    if (pp->pyqtprop_get)
    {
        vret = visit(pp->pyqtprop_get, arg);

        if (vret != 0)
            return vret;
    }

    if (pp->pyqtprop_set)
    {
        vret = visit(pp->pyqtprop_set, arg);

        if (vret != 0)
            return vret;
    }

    if (pp->pyqtprop_del)
    {
        vret = visit(pp->pyqtprop_del, arg);

        if (vret != 0)
            return vret;
    }

    if (pp->pyqtprop_doc)
    {
        vret = visit(pp->pyqtprop_doc, arg);

        if (vret != 0)
            return vret;
    }

    if (pp->pyqtprop_reset)
    {
        vret = visit(pp->pyqtprop_reset, arg);

        if (vret != 0)
            return vret;
    }

    if (pp->pyqtprop_notify)
    {
        vret = visit(pp->pyqtprop_notify, arg);

        if (vret != 0)
            return vret;
    }

    if (pp->pyqtprop_type)
    {
        vret = visit(pp->pyqtprop_type, arg);

        if (vret != 0)
            return vret;
    }

    return 0;
}


// The pyqtProperty init function.
static int pyqtProperty_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    PyObject *type, *get = 0, *set = 0, *reset = 0, *del = 0, *doc = 0,
            *notify = 0;
    int scriptable = 1, stored = 1, user = 0, constant = 0, final = 0;
#if QT_VERSION >= 0x040600
    int designable = 1;
#else
    int designable = -1;
#endif
    static const char *kwlist[] = {"type", "fget", "fset", "freset", "fdel",
            "doc", "designable", "scriptable", "stored", "user", "constant",
            "final", "notify", 0};
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;

    pp->pyqtprop_sequence = pyqtprop_sequence_nr++;

    if (!PyArg_ParseTupleAndKeywords(args, kwds,
#if PY_VERSION_HEX >= 0x02050000
            "O|OOOOOiiiiiiO!:pyqtProperty",
#else
            const_cast<char *>("O|OOOOOiiiiiiO!:pyqtProperty"),
#endif
            const_cast<char **>(kwlist), &type, &get, &set, &reset, &del, &doc,
            &designable, &scriptable, &stored, &user, &constant, &final,
            &qpycore_pyqtSignal_Type, &notify))
        return -1;

    if (get == Py_None)
        get = 0;

    if (set == Py_None)
        set = 0;

    if (del == Py_None)
        del = 0;

    if (reset == Py_None)
        reset = 0;

    if (notify == Py_None)
        notify = 0;

#if QT_VERSION < 0x040600
    // The default value of designable depends on whether the property is
    // writable.
    if (designable < 0)
        designable = (set != 0);
#endif

    // Parse the type.
    const Chimera *ptype = Chimera::parse(type);

    if (!ptype)
    {
        Chimera::raiseParseException(type, "a property");
        return -1;
    }

    pp->pyqtprop_parsed_type = ptype;

    Py_XINCREF(get);
    Py_XINCREF(set);
    Py_XINCREF(del);
    Py_XINCREF(doc);
    Py_XINCREF(reset);
    Py_XINCREF(notify);
    Py_INCREF(type);

    /* If no docstring was given and the getter has one, then use it. */
    if ((!doc || doc == Py_None) && get)
    {
#if PY_VERSION_HEX >= 0x02050000
        PyObject *get_doc = PyObject_GetAttrString(get, "__doc__");
#else
        PyObject *get_doc = PyObject_GetAttrString(get, const_cast<char *>("__doc__"));
#endif

        if (get_doc)
        {
            Py_XDECREF(doc);
            doc = get_doc;
        }
        else
        {
            PyErr_Clear();
        }
    }

    pp->pyqtprop_get = get;
    pp->pyqtprop_set = set;
    pp->pyqtprop_del = del;
    pp->pyqtprop_doc = doc;
    pp->pyqtprop_reset = reset;
    pp->pyqtprop_notify = notify;
    pp->pyqtprop_type = type;

    // ResolveEditable is always set.
    unsigned flags = 0x00080000;

    if (designable)
        flags |= 0x00001000;

    if (scriptable)
        flags |= 0x00004000;

    if (stored)
        flags |= 0x00010000;

    if (user)
        flags |= 0x00100000;

    if (constant)
        flags |= 0x00000400;

    if (final)
        flags |= 0x00000800;

    pp->pyqtprop_flags = flags;

    return 0;
}


// Calling the property is a shorthand way of changing the getter.
static PyObject *pyqtProperty_call(PyObject *self, PyObject *args,
        PyObject *kwds)
{
    PyObject *get;
    static const char *kwlist[] = {"fget", 0};

    if (!PyArg_ParseTupleAndKeywords(args, kwds,
#if PY_VERSION_HEX >= 0x02050000
            "O:pyqtProperty",
#else
            const_cast<char *>("O:pyqtProperty"),
#endif
            const_cast<char **>(kwlist), &get))
        return 0;

    return pyqtProperty_getter(self, get);
}


// Return a copy of the property with a different getter.
static PyObject *pyqtProperty_getter(PyObject *self, PyObject *getter)
{
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;

    pp = clone(pp);

    if (pp)
    {
        Py_XDECREF(pp->pyqtprop_get);

        if (getter == Py_None)
            getter = 0;
        else
            Py_INCREF(getter);

        pp->pyqtprop_get = getter;
    }

    return (PyObject *)pp;
}


// Return a copy of the property with a different setter.
static PyObject *pyqtProperty_setter(PyObject *self, PyObject *setter)
{
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;

    pp = clone(pp);

    if (pp)
    {
        Py_XDECREF(pp->pyqtprop_set);

        if (setter == Py_None)
            setter = 0;
        else
            Py_INCREF(setter);

        pp->pyqtprop_set = setter;
    }

    return (PyObject *)pp;
}


// Return a copy of the property with a different deleter.
static PyObject *pyqtProperty_deleter(PyObject *self, PyObject *deleter)
{
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;

    pp = clone(pp);

    if (pp)
    {
        Py_XDECREF(pp->pyqtprop_del);

        if (deleter == Py_None)
            deleter = 0;
        else
            Py_INCREF(deleter);

        pp->pyqtprop_del = deleter;
    }

    return (PyObject *)pp;
}


// Return a copy of the property with a different reset.
static PyObject *pyqtProperty_reset(PyObject *self, PyObject *reset)
{
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)self;

    pp = clone(pp);

    if (pp)
    {
        Py_XDECREF(pp->pyqtprop_reset);

        if (reset == Py_None)
            reset = 0;
        else
            Py_INCREF(reset);

        pp->pyqtprop_reset = reset;
    }

    return (PyObject *)pp;
}


// Create a clone of a property.
static qpycore_pyqtProperty *clone(qpycore_pyqtProperty *orig)
{
    qpycore_pyqtProperty *pp = (qpycore_pyqtProperty *)PyType_GenericNew(
            Py_TYPE(orig), 0, 0);

    if (pp)
    {
        pp->pyqtprop_get = orig->pyqtprop_get;
        Py_XINCREF(pp->pyqtprop_get);

        pp->pyqtprop_set = orig->pyqtprop_set;
        Py_XINCREF(pp->pyqtprop_set);

        pp->pyqtprop_del = orig->pyqtprop_del;
        Py_XINCREF(pp->pyqtprop_del);

        pp->pyqtprop_doc = orig->pyqtprop_doc;
        Py_XINCREF(pp->pyqtprop_doc);

        pp->pyqtprop_reset = orig->pyqtprop_reset;
        Py_XINCREF(pp->pyqtprop_reset);

        pp->pyqtprop_notify = orig->pyqtprop_notify;
        Py_XINCREF(pp->pyqtprop_notify);

        pp->pyqtprop_type = orig->pyqtprop_type;
        Py_XINCREF(pp->pyqtprop_type);

        pp->pyqtprop_parsed_type = new Chimera(*orig->pyqtprop_parsed_type);

        pp->pyqtprop_flags = orig->pyqtprop_flags;
        pp->pyqtprop_sequence = orig->pyqtprop_sequence;
    }

    return pp;
}
