from bgenOutput import *
from bgenGeneratorGroup import GeneratorGroup

class ObjectDefinition(GeneratorGroup):
    "Spit out code that together defines a new Python object type"
    basechain = "NULL"
    tp_flags = "Py_TPFLAGS_DEFAULT"
    basetype = None
    argref = ""    # set to "*" if arg to <type>_New should be pointer
    argconst = ""   # set to "const " if arg to <type>_New should be const

    def __init__(self, name, prefix, itselftype):
        """ObjectDefinition constructor.  May be extended, but do not override.

        - name: the object's official name, e.g. 'SndChannel'.
        - prefix: the prefix used for the object's functions and data, e.g. 'SndCh'.
        - itselftype: the C type actually contained in the object, e.g. 'SndChannelPtr'.

        XXX For official Python data types, rules for the 'Py' prefix are a problem.
        """

        GeneratorGroup.__init__(self, prefix or name)
        self.name = name
        self.itselftype = itselftype
        self.objecttype = name + 'Object'
        self.typename = name + '_Type'
        self.static = "static " # set to "" to make <type>_New and <type>_Convert public
        self.modulename = None
        if hasattr(self, "assertions"):
            self.assertions()

    def add(self, g, dupcheck=0):
        g.setselftype(self.objecttype, self.itselftype)
        GeneratorGroup.add(self, g, dupcheck)

    def reference(self):
        # In case we are referenced from a module
        pass

    def setmodulename(self, name):
        self.modulename = name

    def generate(self):
        # XXX This should use long strings and %(varname)s substitution!

        OutHeader2("Object type " + self.name)

        self.outputCheck()

        Output("typedef struct %s {", self.objecttype)
        IndentLevel()
        Output("PyObject_HEAD")
        self.outputStructMembers()
        DedentLevel()
        Output("} %s;", self.objecttype)

        self.outputNew()

        self.outputConvert()

        self.outputDealloc()

        GeneratorGroup.generate(self)

        Output()
        self.outputMethodChain()

        self.outputGetattr()

        self.outputSetattr()

        self.outputCompare()

        self.outputRepr()

        self.outputHash()

        self.outputPEP253Hooks()

        self.outputTypeObject()

        OutHeader2("End object type " + self.name)

    def outputCheck(self):
        sf = self.static and "static "
        Output("%sPyTypeObject %s;", sf, self.typename)
        Output()
        Output("#define %s_Check(x) ((x)->ob_type == &%s || PyObject_TypeCheck((x), &%s))",
               self.prefix, self.typename, self.typename)
        Output()

    def outputMethodChain(self):
        Output("%sPyMethodChain %s_chain = { %s_methods, %s };",
                self.static,    self.prefix, self.prefix, self.basechain)

    def outputStructMembers(self):
        Output("%s ob_itself;", self.itselftype)

    def outputNew(self):
        Output()
        Output("%sPyObject *%s_New(%s%s %sitself)", self.static, self.prefix,
                self.argconst, self.itselftype, self.argref)
        OutLbrace()
        Output("%s *it;", self.objecttype)
        self.outputCheckNewArg()
        Output("it = PyObject_NEW(%s, &%s);", self.objecttype, self.typename)
        Output("if (it == NULL) return NULL;")
        if self.basetype:
            Output("/* XXXX Should we tp_init or tp_new our basetype? */")
        self.outputInitStructMembers()
        Output("return (PyObject *)it;")
        OutRbrace()

    def outputInitStructMembers(self):
        Output("it->ob_itself = %sitself;", self.argref)

    def outputCheckNewArg(self):
        "Override this method to apply additional checks/conversions"

    def outputConvert(self):
        Output()
        Output("%sint %s_Convert(PyObject *v, %s *p_itself)", self.static, self.prefix,
                self.itselftype)
        OutLbrace()
        self.outputCheckConvertArg()
        Output("if (!%s_Check(v))", self.prefix)
        OutLbrace()
        Output('PyErr_SetString(PyExc_TypeError, "%s required");', self.name)
        Output("return 0;")
        OutRbrace()
        Output("*p_itself = ((%s *)v)->ob_itself;", self.objecttype)
        Output("return 1;")
        OutRbrace()

    def outputCheckConvertArg(self):
        "Override this method to apply additional conversions"

    def outputDealloc(self):
        Output()
        Output("static void %s_dealloc(%s *self)", self.prefix, self.objecttype)
        OutLbrace()
        self.outputCleanupStructMembers()
        if self.basetype:
            Output("%s.tp_dealloc((PyObject *)self);", self.basetype)
        elif hasattr(self, 'output_tp_free'):
            # This is a new-style object with tp_free slot
            Output("self->ob_type->tp_free((PyObject *)self);")
        else:
            Output("PyObject_Free((PyObject *)self);")
        OutRbrace()

    def outputCleanupStructMembers(self):
        self.outputFreeIt("self->ob_itself")

    def outputFreeIt(self, name):
        Output("/* Cleanup of %s goes here */", name)

    def outputGetattr(self):
        Output()
        Output("static PyObject *%s_getattr(%s *self, char *name)", self.prefix, self.objecttype)
        OutLbrace()
        self.outputGetattrBody()
        OutRbrace()

    def outputGetattrBody(self):
        self.outputGetattrHook()
        Output("return Py_FindMethodInChain(&%s_chain, (PyObject *)self, name);",
               self.prefix)

    def outputGetattrHook(self):
        pass

    def outputSetattr(self):
        Output()
        Output("#define %s_setattr NULL", self.prefix)

    def outputCompare(self):
        Output()
        Output("#define %s_compare NULL", self.prefix)

    def outputRepr(self):
        Output()
        Output("#define %s_repr NULL", self.prefix)

    def outputHash(self):
        Output()
        Output("#define %s_hash NULL", self.prefix)

    def outputTypeObject(self):
        sf = self.static and "static "
        Output()
        Output("%sPyTypeObject %s = {", sf, self.typename)
        IndentLevel()
        Output("PyObject_HEAD_INIT(NULL)")
        Output("0, /*ob_size*/")
        if self.modulename:
            Output("\"%s.%s\", /*tp_name*/", self.modulename, self.name)
        else:
            Output("\"%s\", /*tp_name*/", self.name)
        Output("sizeof(%s), /*tp_basicsize*/", self.objecttype)
        Output("0, /*tp_itemsize*/")
        Output("/* methods */")
        Output("(destructor) %s_dealloc, /*tp_dealloc*/", self.prefix)
        Output("0, /*tp_print*/")
        Output("(getattrfunc) %s_getattr, /*tp_getattr*/", self.prefix)
        Output("(setattrfunc) %s_setattr, /*tp_setattr*/", self.prefix)
        Output("(cmpfunc) %s_compare, /*tp_compare*/", self.prefix)
        Output("(reprfunc) %s_repr, /*tp_repr*/", self.prefix)
        Output("(PyNumberMethods *)0, /* tp_as_number */")
        Output("(PySequenceMethods *)0, /* tp_as_sequence */")
        Output("(PyMappingMethods *)0, /* tp_as_mapping */")
        Output("(hashfunc) %s_hash, /*tp_hash*/", self.prefix)
        DedentLevel()
        Output("};")

    def outputTypeObjectInitializer(self):
        Output("""%s.ob_type = &PyType_Type;""", self.typename)
        if self.basetype:
            Output("%s.tp_base = &%s;", self.typename, self.basetype)
        Output("if (PyType_Ready(&%s) < 0) return;", self.typename)
        Output("""Py_INCREF(&%s);""", self.typename)
        Output("PyModule_AddObject(m, \"%s\", (PyObject *)&%s);", self.name, self.typename);
        self.outputTypeObjectInitializerCompat()

    def outputTypeObjectInitializerCompat(self):
        Output("/* Backward-compatible name */")
        Output("""Py_INCREF(&%s);""", self.typename);
        Output("PyModule_AddObject(m, \"%sType\", (PyObject *)&%s);", self.name, self.typename);

    def outputPEP253Hooks(self):
        pass

class PEP252Mixin:
    getsetlist = []

    def assertions(self):
        # Check that various things aren't overridden. If they are it could
        # signify a bgen-client that has been partially converted to PEP252.
        assert self.outputGetattr.im_func == PEP252Mixin.outputGetattr.im_func
        assert self.outputSetattr.im_func == PEP252Mixin.outputSetattr.im_func
        assert self.outputGetattrBody == None
        assert self.outputGetattrHook == None
        assert self.basechain == "NULL"

    def outputGetattr(self):
        pass

    outputGetattrBody = None

    outputGetattrHook = None

    def outputSetattr(self):
        pass

    def outputMethodChain(self):
        # This is a good place to output the getters and setters
        self.outputGetSetList()

    def outputHook(self, name):
        methodname = "outputHook_" + name
        if hasattr(self, methodname):
            func = getattr(self, methodname)
            func()
        else:
            Output("0, /*%s*/", name)

    def outputTypeObject(self):
        sf = self.static and "static "
        Output()
        Output("%sPyTypeObject %s = {", sf, self.typename)
        IndentLevel()
        Output("PyObject_HEAD_INIT(NULL)")
        Output("0, /*ob_size*/")
        if self.modulename:
            Output("\"%s.%s\", /*tp_name*/", self.modulename, self.name)
        else:
            Output("\"%s\", /*tp_name*/", self.name)
        Output("sizeof(%s), /*tp_basicsize*/", self.objecttype)
        Output("0, /*tp_itemsize*/")

        Output("/* methods */")
        Output("(destructor) %s_dealloc, /*tp_dealloc*/", self.prefix)
        Output("0, /*tp_print*/")
        Output("(getattrfunc)0, /*tp_getattr*/")
        Output("(setattrfunc)0, /*tp_setattr*/")
        Output("(cmpfunc) %s_compare, /*tp_compare*/", self.prefix)
        Output("(reprfunc) %s_repr, /*tp_repr*/", self.prefix)

        Output("(PyNumberMethods *)0, /* tp_as_number */")
        Output("(PySequenceMethods *)0, /* tp_as_sequence */")
        Output("(PyMappingMethods *)0, /* tp_as_mapping */")

        Output("(hashfunc) %s_hash, /*tp_hash*/", self.prefix)
        self.outputHook("tp_call")
        Output("0, /*tp_str*/")
        Output("PyObject_GenericGetAttr, /*tp_getattro*/")
        Output("PyObject_GenericSetAttr, /*tp_setattro */")

        self.outputHook("tp_as_buffer")
        Output("%s, /* tp_flags */", self.tp_flags)
        self.outputHook("tp_doc")
        self.outputHook("tp_traverse")
        self.outputHook("tp_clear")
        self.outputHook("tp_richcompare")
        self.outputHook("tp_weaklistoffset")
        self.outputHook("tp_iter")
        self.outputHook("tp_iternext")
        Output("%s_methods, /* tp_methods */", self.prefix)
        self.outputHook("tp_members")
        Output("%s_getsetlist, /*tp_getset*/", self.prefix)
        self.outputHook("tp_base")
        self.outputHook("tp_dict")
        self.outputHook("tp_descr_get")
        self.outputHook("tp_descr_set")
        self.outputHook("tp_dictoffset")
        self.outputHook("tp_init")
        self.outputHook("tp_alloc")
        self.outputHook("tp_new")
        self.outputHook("tp_free")
        DedentLevel()
        Output("};")

    def outputGetSetList(self):
        if self.getsetlist:
            for name, get, set, doc in self.getsetlist:
                if get:
                    self.outputGetter(name, get)
                else:
                    Output("#define %s_get_%s NULL", self.prefix, name)
                    Output()
                if set:
                    self.outputSetter(name, set)
                else:
                    Output("#define %s_set_%s NULL", self.prefix, name)
                    Output()

            Output("static PyGetSetDef %s_getsetlist[] = {", self.prefix)
            IndentLevel()
            for name, get, set, doc in self.getsetlist:
                if doc:
                    doc = '"' + doc + '"'
                else:
                    doc = "NULL"
                Output("{\"%s\", (getter)%s_get_%s, (setter)%s_set_%s, %s},",
                    name, self.prefix, name, self.prefix, name, doc)
            Output("{NULL, NULL, NULL, NULL},")
            DedentLevel()
            Output("};")
        else:
            Output("#define %s_getsetlist NULL", self.prefix)
        Output()

    def outputGetter(self, name, code):
        Output("static PyObject *%s_get_%s(%s *self, void *closure)",
            self.prefix, name, self.objecttype)
        OutLbrace()
        Output(code)
        OutRbrace()
        Output()

    def outputSetter(self, name, code):
        Output("static int %s_set_%s(%s *self, PyObject *v, void *closure)",
            self.prefix, name, self.objecttype)
        OutLbrace()
        Output(code)
        Output("return 0;")
        OutRbrace()
        Output()

class PEP253Mixin(PEP252Mixin):
    tp_flags = "Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE"

    def outputHook_tp_init(self):
        Output("%s_tp_init, /* tp_init */", self.prefix)

    def outputHook_tp_alloc(self):
        Output("%s_tp_alloc, /* tp_alloc */", self.prefix)

    def outputHook_tp_new(self):
        Output("%s_tp_new, /* tp_new */", self.prefix)

    def outputHook_tp_free(self):
        Output("%s_tp_free, /* tp_free */", self.prefix)

    def output_tp_initBody_basecall(self):
        """If a type shares its init call with its base type set output_tp_initBody
        to output_tp_initBody_basecall"""
        if self.basetype:
            Output("if (%s.tp_init)", self.basetype)
            OutLbrace()
            Output("if ( (*%s.tp_init)(_self, _args, _kwds) < 0) return -1;", self.basetype)
            OutRbrace()

    output_tp_initBody = None

    def output_tp_init(self):
        if self.output_tp_initBody:
            Output("static int %s_tp_init(PyObject *_self, PyObject *_args, PyObject *_kwds)", self.prefix)
            OutLbrace()
            self.output_tp_initBody()
            OutRbrace()
        else:
            Output("#define %s_tp_init 0", self.prefix)
        Output()

    output_tp_allocBody = None

    def output_tp_alloc(self):
        if self.output_tp_allocBody:
            Output("static PyObject *%s_tp_alloc(PyTypeObject *type, int nitems)",
                self.prefix)
            OutLbrace()
            self.output_tp_allocBody()
            OutRbrace()
        else:
            Output("#define %s_tp_alloc PyType_GenericAlloc", self.prefix)
        Output()

    def output_tp_newBody(self):
        Output("PyObject *_self;");
        Output("%s itself;", self.itselftype);
        Output("char *kw[] = {\"itself\", 0};")
        Output()
        Output("if (!PyArg_ParseTupleAndKeywords(_args, _kwds, \"O&\", kw, %s_Convert, &itself)) return NULL;",
            self.prefix);
        if self.basetype:
            Output("if (%s.tp_new)", self.basetype)
            OutLbrace()
            Output("if ( (*%s.tp_new)(type, _args, _kwds) == NULL) return NULL;", self.basetype)
            Dedent()
            Output("} else {")
            Indent()
            Output("if ((_self = type->tp_alloc(type, 0)) == NULL) return NULL;")
            OutRbrace()
        else:
            Output("if ((_self = type->tp_alloc(type, 0)) == NULL) return NULL;")
        Output("((%s *)_self)->ob_itself = itself;", self.objecttype)
        Output("return _self;")

    def output_tp_new(self):
        if self.output_tp_newBody:
            Output("static PyObject *%s_tp_new(PyTypeObject *type, PyObject *_args, PyObject *_kwds)", self.prefix)
            OutLbrace()
            self.output_tp_newBody()
            OutRbrace()
        else:
            Output("#define %s_tp_new PyType_GenericNew", self.prefix)
        Output()

    output_tp_freeBody = None

    def output_tp_free(self):
        if self.output_tp_freeBody:
            Output("static void %s_tp_free(PyObject *self)", self.prefix)
            OutLbrace()
            self.output_tp_freeBody()
            OutRbrace()
        else:
            Output("#define %s_tp_free PyObject_Del", self.prefix)
        Output()

    def outputPEP253Hooks(self):
        self.output_tp_init()
        self.output_tp_alloc()
        self.output_tp_new()
        self.output_tp_free()

class GlobalObjectDefinition(ObjectDefinition):
    """Like ObjectDefinition but exports some parts.

    XXX Should also somehow generate a .h file for them.
    """

    def __init__(self, name, prefix = None, itselftype = None):
        ObjectDefinition.__init__(self, name, prefix or name, itselftype or name)
        self.static = ""

class ObjectIdentityMixin:
    """A mixin class for objects that makes the identity of ob_itself
    govern comparisons and dictionary lookups. Useful if the C object can
    be returned by library calls and it is difficult (or impossible) to find
    the corresponding Python objects. With this you can create Python object
    wrappers on the fly"""

    def outputCompare(self):
        Output()
        Output("static int %s_compare(%s *self, %s *other)", self.prefix, self.objecttype,
                self.objecttype)
        OutLbrace()
        Output("unsigned long v, w;")
        Output()
        Output("if (!%s_Check((PyObject *)other))", self.prefix)
        OutLbrace()
        Output("v=(unsigned long)self;")
        Output("w=(unsigned long)other;")
        OutRbrace()
        Output("else")
        OutLbrace()
        Output("v=(unsigned long)self->ob_itself;")
        Output("w=(unsigned long)other->ob_itself;")
        OutRbrace()
        Output("if( v < w ) return -1;")
        Output("if( v > w ) return 1;")
        Output("return 0;")
        OutRbrace()

    def outputHash(self):
        Output()
        Output("static long %s_hash(%s *self)", self.prefix, self.objecttype)
        OutLbrace()
        Output("return (long)self->ob_itself;")
        OutRbrace()
