import warnings

from typehandlers.base import ForwardWrapperBase, ReverseWrapperBase, \
    Parameter, ReturnValue, TypeConfigurationError, NotSupportedError

import cppclass


###
### ------------ C++ class parameter type handlers ------------
###
class CppClassParameterBase(Parameter):
    "Base class for all C++ Class parameter handlers"
    CTYPES = []
    cpp_class = cppclass.CppClass('dummy') # CppClass instance
    DIRECTIONS = [Parameter.DIRECTION_IN]

    def __init__(self, ctype, name, direction=Parameter.DIRECTION_IN, is_const=False, default_value=None):
        """
        ctype -- C type, normally 'MyClass*'
        name -- parameter name
        """
        if ctype == self.cpp_class.name:
            ctype = self.cpp_class.full_name
        super(CppClassParameterBase, self).__init__(
            ctype, name, direction, is_const, default_value)

        ## name of the PyFoo * variable used in parameter parsing
        self.py_name = None

        ## it True, this parameter is 'fake', and instead of being
        ## passed a parameter from python it is assumed to be the
        ## 'self' parameter of a method wrapper
        self.take_value_from_python_self = False


class CppClassReturnValueBase(ReturnValue):
    "Class return handlers -- base class"
    CTYPES = []
    cpp_class = cppclass.CppClass('dummy') # CppClass instance

    def __init__(self, ctype, is_const=False):
        super(CppClassReturnValueBase, self).__init__(ctype, is_const=is_const)
        ## name of the PyFoo * variable used in return value building
        self.py_name = None


class CppClassParameter(CppClassParameterBase):
    "Class handlers"
    CTYPES = []
    cpp_class = cppclass.CppClass('dummy') # CppClass instance
    DIRECTIONS = [Parameter.DIRECTION_IN]
    
    def convert_python_to_c(self, wrapper):
        "parses python args to get C++ value"
        assert isinstance(wrapper, ForwardWrapperBase)
        assert isinstance(self.cpp_class, cppclass.CppClass)

        if self.take_value_from_python_self:
            self.py_name = 'self'
            wrapper.call_params.append(
                '*((%s *) %s)->obj' % (self.cpp_class.pystruct, self.py_name))
        else:
            implicit_conversion_sources = self.cpp_class.get_all_implicit_conversions()
            if not implicit_conversion_sources:
                if self.default_value is not None:
                    self.cpp_class.get_construct_name() # raises an exception if the class cannot be constructed
                    self.py_name = wrapper.declarations.declare_variable(
                        self.cpp_class.pystruct+'*', self.name, 'NULL')
                    wrapper.parse_params.add_parameter(
                        'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name, optional=True)
                    wrapper.call_params.append(
                        '(%s ? (*((%s *) %s)->obj) : %s)' % (self.py_name, self.cpp_class.pystruct, self.py_name, self.default_value))
                else:
                    self.py_name = wrapper.declarations.declare_variable(
                        self.cpp_class.pystruct+'*', self.name)
                    wrapper.parse_params.add_parameter(
                        'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name)
                    wrapper.call_params.append(
                        '*((%s *) %s)->obj' % (self.cpp_class.pystruct, self.py_name))
            else:
                if self.default_value is None:
                    self.py_name = wrapper.declarations.declare_variable(
                        'PyObject*', self.name)
                    tmp_value_variable = wrapper.declarations.declare_variable(
                        self.cpp_class.full_name, self.name)
                    wrapper.parse_params.add_parameter('O', ['&'+self.py_name], self.name)
                else:
                    self.py_name = wrapper.declarations.declare_variable(
                        'PyObject*', self.name, 'NULL')
                    tmp_value_variable = wrapper.declarations.declare_variable(
                        self.cpp_class.full_name, self.name)
                    wrapper.parse_params.add_parameter('O', ['&'+self.py_name], self.name, optional=True)

                if self.default_value is None:
                    wrapper.before_call.write_code("if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
                                                   "    %s = *((%s *) %s)->obj;" %
                                                   (self.py_name, self.cpp_class.pytypestruct,
                                                    tmp_value_variable,
                                                    self.cpp_class.pystruct, self.py_name))
                else:
                    wrapper.before_call.write_code(
                        "if (%s == NULL) {\n"
                        "    %s = %s;" %
                        (self.py_name, tmp_value_variable, self.default_value))
                    wrapper.before_call.write_code(
                        "} else if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
                                                   "    %s = *((%s *) %s)->obj;" %
                                                   (self.py_name, self.cpp_class.pytypestruct,
                                                    tmp_value_variable,
                                                    self.cpp_class.pystruct, self.py_name))
                for conversion_source in implicit_conversion_sources:
                    wrapper.before_call.write_code("} else if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
                                                   "    %s = *((%s *) %s)->obj;" %
                                                   (self.py_name, conversion_source.pytypestruct,
                                                    tmp_value_variable,
                                                    conversion_source.pystruct, self.py_name))
                wrapper.before_call.write_code("} else {\n")
                wrapper.before_call.indent()
                possible_type_names = ", ".join([cls.name for cls in [self.cpp_class] + implicit_conversion_sources])
                wrapper.before_call.write_code("PyErr_Format(PyExc_TypeError, \"parameter must an instance of one of the types (%s), not %%s\", %s->ob_type->tp_name);" % (possible_type_names, self.py_name))
                wrapper.before_call.write_error_return()
                wrapper.before_call.unindent()
                wrapper.before_call.write_code("}")

                wrapper.call_params.append(tmp_value_variable)

    def convert_c_to_python(self, wrapper):
        '''Write some code before calling the Python method.'''
        assert isinstance(wrapper, ReverseWrapperBase)

        self.py_name = wrapper.declarations.declare_variable(
            self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
        if self.cpp_class.allow_subclassing:
            new_func = 'PyObject_GC_New'
        else:
            new_func = 'PyObject_New'
        wrapper.before_call.write_code(
            "%s = %s(%s, %s);" %
            (self.py_name, new_func, self.cpp_class.pystruct, '&'+self.cpp_class.pytypestruct))
        if self.cpp_class.allow_subclassing:
            wrapper.before_call.write_code(
                "%s->inst_dict = NULL;" % (self.py_name,))

        self.cpp_class.write_create_instance(wrapper.before_call,
                                             "%s->obj" % self.py_name,
                                             self.value)
        self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, self.py_name,
                                                                   "%s->obj" % self.py_name)

        wrapper.build_params.add_parameter("N", [self.py_name])


class CppClassRefParameter(CppClassParameterBase):
    "Class& handlers"
    CTYPES = []
    cpp_class = cppclass.CppClass('dummy') # CppClass instance
    DIRECTIONS = [Parameter.DIRECTION_IN,
                  Parameter.DIRECTION_OUT,
                  Parameter.DIRECTION_INOUT]

    def __init__(self, ctype, name, direction=Parameter.DIRECTION_IN, is_const=False,
                 default_value=None, default_value_type=None):
        """
        @param ctype: C type, normally 'MyClass*'
        @param name: parameter name
        """
        if ctype == self.cpp_class.name:
            ctype = self.cpp_class.full_name
        super(CppClassRefParameter, self).__init__(
            ctype, name, direction, is_const, default_value)
        self.default_value_type = default_value_type
    
    def convert_python_to_c(self, wrapper):
        "parses python args to get C++ value"
        assert isinstance(wrapper, ForwardWrapperBase)
        assert isinstance(self.cpp_class, cppclass.CppClass)

        if self.direction == Parameter.DIRECTION_IN:
            if self.take_value_from_python_self:
                self.py_name = 'self'
                wrapper.call_params.append(
                    '*((%s *) %s)->obj' % (self.cpp_class.pystruct, self.py_name))
            else:
                implicit_conversion_sources = self.cpp_class.get_all_implicit_conversions()
                if not (implicit_conversion_sources and self.type_traits.target_is_const):
                    if self.default_value is not None:
                        self.py_name = wrapper.declarations.declare_variable(
                            self.cpp_class.pystruct+'*', self.name, 'NULL')

                        wrapper.parse_params.add_parameter(
                            'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name, optional=True)

                        if self.default_value_type is not None:
                            default_value_name = wrapper.declarations.declare_variable(
                                self.default_value_type, "%s_default" % self.name,
                                self.default_value)
                            wrapper.call_params.append(
                                '(%s ? (*((%s *) %s)->obj) : %s)' % (self.py_name, self.cpp_class.pystruct,
                                                                     self.py_name, default_value_name))
                        else:
                            self.cpp_class.get_construct_name() # raises an exception if the class cannot be constructed
                            wrapper.call_params.append(
                                '(%s ? (*((%s *) %s)->obj) : %s)' % (self.py_name, self.cpp_class.pystruct,
                                                                     self.py_name, self.default_value))
                    else:
                        self.py_name = wrapper.declarations.declare_variable(
                            self.cpp_class.pystruct+'*', self.name)
                        wrapper.parse_params.add_parameter(
                            'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name)
                        wrapper.call_params.append(
                            '*((%s *) %s)->obj' % (self.cpp_class.pystruct, self.py_name))
                else:
                    if self.default_value is not None:
                        warnings.warn("with implicit conversions, default value "
                                      "in C++ class reference parameters is ignored.")
                    self.py_name = wrapper.declarations.declare_variable(
                        'PyObject*', self.name)
                    tmp_value_variable = wrapper.declarations.declare_variable(
                        self.cpp_class.full_name, self.name)
                    wrapper.parse_params.add_parameter('O', ['&'+self.py_name], self.name)

                    wrapper.before_call.write_code("if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
                                                   "    %s = *((%s *) %s)->obj;" %
                                                   (self.py_name, self.cpp_class.pytypestruct,
                                                    tmp_value_variable,
                                                    self.cpp_class.pystruct, self.py_name))
                    for conversion_source in implicit_conversion_sources:
                        wrapper.before_call.write_code("} else if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
                                                       "    %s = *((%s *) %s)->obj;" %
                                                       (self.py_name, conversion_source.pytypestruct,
                                                        tmp_value_variable,
                                                        conversion_source.pystruct, self.py_name))
                    wrapper.before_call.write_code("} else {\n")
                    wrapper.before_call.indent()
                    possible_type_names = ", ".join([cls.name for cls in [self.cpp_class] + implicit_conversion_sources])
                    wrapper.before_call.write_code("PyErr_Format(PyExc_TypeError, \"parameter must an instance of one of the types (%s), not %%s\", %s->ob_type->tp_name);" % (possible_type_names, self.py_name))
                    wrapper.before_call.write_error_return()
                    wrapper.before_call.unindent()
                    wrapper.before_call.write_code("}")

                    wrapper.call_params.append(tmp_value_variable)

        elif self.direction == Parameter.DIRECTION_OUT:
            assert not self.take_value_from_python_self

            self.py_name = wrapper.declarations.declare_variable(
                self.cpp_class.pystruct+'*', self.name)

            if self.cpp_class.allow_subclassing:
                new_func = 'PyObject_GC_New'
            else:
                new_func = 'PyObject_New'
            wrapper.before_call.write_code(
                "%s = %s(%s, %s);" %
                (self.py_name, new_func, self.cpp_class.pystruct,
                 '&'+self.cpp_class.pytypestruct))
            if self.cpp_class.allow_subclassing:
                wrapper.after_call.write_code(
                    "%s->inst_dict = NULL;" % (self.py_name,))

            self.cpp_class.write_create_instance(wrapper.before_call,
                                                 "%s->obj" % self.py_name,
                                                 '')
            self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, self.py_name,
                                                                       "%s->obj" % self.py_name)
            wrapper.call_params.append('*%s->obj' % (self.py_name,))
            wrapper.build_params.add_parameter("N", [self.py_name])

        ## well, personally I think inout here doesn't make much sense
        ## (it's just plain confusing), but might as well support it..

        ## C++ class reference inout parameters allow "inplace"
        ## modifications, i.e. the object is not explicitly returned
        ## but is instead modified by the callee.
        elif self.direction == Parameter.DIRECTION_INOUT:
            assert not self.take_value_from_python_self

            self.py_name = wrapper.declarations.declare_variable(
                self.cpp_class.pystruct+'*', self.name)

            wrapper.parse_params.add_parameter(
                'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name)
            wrapper.call_params.append(
                '*%s->obj' % (self.py_name))

    def convert_c_to_python(self, wrapper):
        '''Write some code before calling the Python method.'''
        assert isinstance(wrapper, ReverseWrapperBase)

        self.py_name = wrapper.declarations.declare_variable(
            self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
        if self.cpp_class.allow_subclassing:
            new_func = 'PyObject_GC_New'
        else:
            new_func = 'PyObject_New'
        wrapper.before_call.write_code(
            "%s = %s(%s, %s);" %
            (self.py_name, new_func, self.cpp_class.pystruct, '&'+self.cpp_class.pytypestruct))
        if self.cpp_class.allow_subclassing:
            wrapper.before_call.write_code(
                "%s->inst_dict = NULL;" % (self.py_name,))

        if self.direction == Parameter.DIRECTION_IN:
            self.cpp_class.write_create_instance(wrapper.before_call,
                                                 "%s->obj" % self.py_name,
                                                 self.value)
            self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, self.py_name,
                                                                       "%s->obj" % self.py_name)
            wrapper.build_params.add_parameter("N", [self.py_name])
        else:
            ## out/inout case:
            ## the callee receives a "temporary wrapper", which loses
            ## the ->obj pointer after the python call; this is so
            ## that the python code directly manipulates the object
            ## received as parameter, instead of a copy.
            if self.type_traits.target_is_const:
                value = "(%s*) (&(%s))" % (self.cpp_class.full_name, self.value)
            else:
                value = "&(%s)" % self.value
            wrapper.before_call.write_code(
                "%s->obj = %s;" % (self.py_name, value))
            wrapper.build_params.add_parameter("O", [self.py_name])
            wrapper.before_call.add_cleanup_code("Py_DECREF(%s);" % self.py_name)

            ## if after the call we notice the callee kept a reference
            ## to the pyobject, we then swap pywrapper->obj for a copy
            ## of the original object.  Else the ->obj pointer is
            ## simply erased (we never owned this object in the first
            ## place).
            wrapper.after_call.write_code(
                "if (%s->ob_refcnt == 1)\n"
                "    %s->obj = NULL;\n"
                "else{\n" % (self.py_name, self.py_name))
            wrapper.after_call.indent()
            self.cpp_class.write_create_instance(wrapper.after_call,
                                                 "%s->obj" % self.py_name,
                                                 self.value)
            self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.after_call, self.py_name,
                                                                       "%s->obj" % self.py_name)
            wrapper.after_call.unindent()
            wrapper.after_call.write_code('}')


class CppClassReturnValue(CppClassReturnValueBase):
    "Class return handlers"
    CTYPES = []
    cpp_class = cppclass.CppClass('dummy') # CppClass instance
    REQUIRES_ASSIGNMENT_CONSTRUCTOR = True

    def __init__(self, ctype, is_const=False):
        """override to fix the ctype parameter with namespace information"""
        if ctype == self.cpp_class.name:
            ctype = self.cpp_class.full_name
        super(CppClassReturnValue, self).__init__(ctype, is_const=is_const)

    def get_c_error_return(self): # only used in reverse wrappers
        """See ReturnValue.get_c_error_return"""
        return "return %s();" % (self.cpp_class.full_name,)

    def convert_c_to_python(self, wrapper):
        """see ReturnValue.convert_c_to_python"""
        py_name = wrapper.declarations.declare_variable(
            self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
        self.py_name = py_name
        if self.cpp_class.allow_subclassing:
            new_func = 'PyObject_GC_New'
        else:
            new_func = 'PyObject_New'
        wrapper.after_call.write_code(
            "%s = %s(%s, %s);" %
            (py_name, new_func, self.cpp_class.pystruct, '&'+self.cpp_class.pytypestruct))
        if self.cpp_class.allow_subclassing:
            wrapper.after_call.write_code(
                "%s->inst_dict = NULL;" % (py_name,))

        self.cpp_class.write_create_instance(wrapper.after_call,
                                             "%s->obj" % py_name,
                                             self.value)
        self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.after_call, py_name,
                                                                   "%s->obj" % py_name)
        wrapper.build_params.add_parameter("N", [py_name], prepend=True)

    def convert_python_to_c(self, wrapper):
        """see ReturnValue.convert_python_to_c"""
        name = wrapper.declarations.declare_variable(
            self.cpp_class.pystruct+'*', "tmp_%s" % self.cpp_class.name)
        wrapper.parse_params.add_parameter(
            'O!', ['&'+self.cpp_class.pytypestruct, '&'+name])
        if self.REQUIRES_ASSIGNMENT_CONSTRUCTOR:
            wrapper.after_call.write_code('%s %s = *%s->obj;' %
                                          (self.cpp_class.full_name, self.value, name))
        else:
            wrapper.after_call.write_code('%s = *%s->obj;' % (self.value, name))
    

class CppClassPtrParameter(CppClassParameterBase):
    "Class* handlers"
    CTYPES = []
    cpp_class = cppclass.CppClass('dummy') # CppClass instance
    DIRECTIONS = [Parameter.DIRECTION_IN]
    SUPPORTS_TRANSFORMATIONS = True

    def __init__(self, ctype, name, transfer_ownership=None, custodian=None, is_const=False,
                 null_ok=False, default_value=None):
        """
        @param ctype: C type, normally 'MyClass*'
        @param name: parameter name

        @param transfer_ownership: this parameter transfer the ownership of
                              the pointed-to object to the called
                              function; should be omitted if custodian
                              is given.
        @param custodian: the object (custodian) that is responsible for
                     managing the life cycle of the parameter.
                     Possible values are:
                       - None: no object is custodian;
                       - -1: the return value object;
                       - 0: the instance of the method in which
                     the ReturnValue is being used will become the
                     custodian;
                       - integer > 0: parameter number, starting at 1
                     (i.e. not counting the self/this parameter),
                     whose object will be used as custodian.

        @param is_const: if true, the parameter has a const attached to the leftmost

        @param null_ok: if true, None is accepted and mapped into a C NULL pointer

        @param default_value: default parameter value (as C expression
        string); probably, the only default value that makes sense
        here is probably 'NULL'.

        @note: only arguments which are instances of C++ classes
        wrapped by PyBindGen can be used as custodians.
        """
        if ctype == self.cpp_class.name:
            ctype = self.cpp_class.full_name
        super(CppClassPtrParameter, self).__init__(
            ctype, name, direction=Parameter.DIRECTION_IN, is_const=is_const)

        if custodian is None:
            if transfer_ownership is None:
                if self.type_traits.target_is_const:
                    transfer_ownership = False
                else:
                    raise TypeConfigurationError("transfer_ownership parameter missing")
            self.transfer_ownership = transfer_ownership
        else:
            if transfer_ownership is not None:
                raise TypeConfigurationError("the transfer_ownership parameter should "
                                             "not be given when there is a custodian")
            self.transfer_ownership = False
        self.custodian = custodian
        self.null_ok = null_ok
        self.default_value = default_value

    def convert_python_to_c(self, wrapper):
        "parses python args to get C++ value"
        assert isinstance(wrapper, ForwardWrapperBase)
        assert isinstance(self.cpp_class, cppclass.CppClass)

        if self.take_value_from_python_self:
            self.py_name = 'self'
            value_ptr = 'self->obj'
        else:
            self.py_name = wrapper.declarations.declare_variable(
                self.cpp_class.pystruct+'*', self.name,
                initializer=(self.default_value and 'NULL' or None))

            value_ptr = wrapper.declarations.declare_variable("%s*" % self.cpp_class.full_name,
                                                              "%s_ptr" % self.name)

            if self.null_ok:
                num = wrapper.parse_params.add_parameter('O', ['&'+self.py_name], self.name, optional=bool(self.default_value))

                wrapper.before_call.write_error_check(

                    "%s && ((PyObject *) %s != Py_None) && !PyObject_IsInstance((PyObject *) %s, (PyObject *) &%s)"
                    % (self.py_name, self.py_name, self.py_name, self.cpp_class.pytypestruct),

                    'PyErr_SetString(PyExc_TypeError, "Parameter %i must be %s");' % (num, self.cpp_class.name))

                wrapper.before_call.write_code("if (%(PYNAME)s) {\n"
                                               "    if ((PyObject *) %(PYNAME)s == Py_None)\n"
                                               "        %(VALUE)s = NULL;\n"
                                               "    else\n"
                                               "        %(VALUE)s = %(PYNAME)s->obj;\n"
                                               "} else {\n"
                                               "    %(VALUE)s = NULL;\n"
                                               "}" % dict(PYNAME=self.py_name, VALUE=value_ptr))

            else:

                wrapper.parse_params.add_parameter(
                    'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name, optional=bool(self.default_value))
                wrapper.before_call.write_code("%s = (%s ? %s->obj : NULL);" % (value_ptr, self.py_name, self.py_name))

        value = self.transformation.transform(self, wrapper.declarations, wrapper.before_call, value_ptr)
        wrapper.call_params.append(value)
        
        if self.transfer_ownership:
            if not isinstance(self.cpp_class.memory_policy, cppclass.ReferenceCountingPolicy):
                # if we transfer ownership, in the end we no longer own the object, so clear our pointer
                wrapper.after_call.write_code('if (%s)\n    %s->obj = NULL;' % (self.py_name, self.py_name,))
            else:
                wrapper.before_call.write_code("if (%s) {" % self.py_name)
                wrapper.before_call.indent()
                self.cpp_class.memory_policy.write_incref(wrapper.before_call, "%s->obj" % self.py_name)
                wrapper.before_call.unindent()
                wrapper.before_call.write_code("}")


    def convert_c_to_python(self, wrapper):
        """foo"""

        ## Value transformations
        value = self.transformation.untransform(
            self, wrapper.declarations, wrapper.after_call, self.value)

        ## declare wrapper variable
        py_name = wrapper.declarations.declare_variable(
            self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
        self.py_name = py_name

        def write_create_new_wrapper():
            """Code path that creates a new wrapper for the parameter"""

            ## Find out what Python wrapper to use, in case
            ## automatic_type_narrowing is active and we are not forced to
            ## make a copy of the object
            if (self.cpp_class.automatic_type_narrowing
                and (self.transfer_ownership or isinstance(self.cpp_class.memory_policy,
                                                           cppclass.ReferenceCountingPolicy))):

                typeid_map_name = self.cpp_class.get_type_narrowing_root().typeid_map_name
                wrapper_type = wrapper.declarations.declare_variable(
                    'PyTypeObject*', 'wrapper_type', '0')
                wrapper.before_call.write_code(
                    '%s = %s.lookup_wrapper(typeid(*%s), &%s);'
                    % (wrapper_type, typeid_map_name, value, self.cpp_class.pytypestruct))
            else:
                wrapper_type = '&'+self.cpp_class.pytypestruct

            ## Create the Python wrapper object
            if self.cpp_class.allow_subclassing:
                new_func = 'PyObject_GC_New'
            else:
                new_func = 'PyObject_New'
            wrapper.before_call.write_code(
                "%s = %s(%s, %s);" %
                (py_name, new_func, self.cpp_class.pystruct, wrapper_type))
            self.py_name = py_name

            if self.cpp_class.allow_subclassing:
                wrapper.before_call.write_code(
                    "%s->inst_dict = NULL;" % (py_name,))

            ## Assign the C++ value to the Python wrapper
            if self.transfer_ownership:
                wrapper.before_call.write_code("%s->obj = %s;" % (py_name, value))
            else:
                if not isinstance(self.cpp_class.memory_policy, cppclass.ReferenceCountingPolicy):
                    ## The PyObject gets a temporary pointer to the
                    ## original value; the pointer is converted to a
                    ## copy in case the callee retains a reference to
                    ## the object after the call.

                    if self.direction == Parameter.DIRECTION_IN:
                        self.cpp_class.write_create_instance(wrapper.before_call,
                                                             "%s->obj" % self.py_name,
                                                             '*'+self.value)
                    else:
                        ## out/inout case:
                        ## the callee receives a "temporary wrapper", which loses
                        ## the ->obj pointer after the python call; this is so
                        ## that the python code directly manipulates the object
                        ## received as parameter, instead of a copy.
                        if self.type_traits.target_is_const:
                            unconst_value = "(%s*) (%s)" % (self.cpp_class.full_name, value)
                        else:
                            unconst_value = value
                        wrapper.before_call.write_code(
                            "%s->obj = %s;" % (self.py_name, unconst_value))
                        wrapper.build_params.add_parameter("O", [self.py_name])
                        wrapper.before_call.add_cleanup_code("Py_DECREF(%s);" % self.py_name)

                        ## if after the call we notice the callee kept a reference
                        ## to the pyobject, we then swap pywrapper->obj for a copy
                        ## of the original object.  Else the ->obj pointer is
                        ## simply erased (we never owned this object in the first
                        ## place).
                        wrapper.after_call.write_code(
                            "if (%s->ob_refcnt == 1)\n"
                            "    %s->obj = NULL;\n"
                            "else {\n" % (self.py_name, self.py_name))
                        wrapper.after_call.indent()
                        self.cpp_class.write_create_instance(wrapper.after_call,
                                                             "%s->obj" % self.py_name,
                                                             '*'+value)
                        wrapper.after_call.unindent()
                        wrapper.after_call.write_code('}')
                else:
                    ## The PyObject gets a new reference to the same obj
                    self.cpp_class.memory_policy.write_incref(wrapper.before_call, value)
                    if self.type_traits.target_is_const:
                        wrapper.before_call.write_code("%s->obj = (%s*) (%s);" %
                                                       (py_name, self.cpp_class.full_name, value))
                    else:
                        wrapper.before_call.write_code("%s->obj = %s;" % (py_name, value))
        ## closes def write_create_new_wrapper():

        if self.cpp_class.helper_class is None:
            try:
                self.cpp_class.wrapper_registry.write_lookup_wrapper(
                    wrapper.before_call, self.cpp_class.pystruct, py_name, value)
            except NotSupportedError:
                write_create_new_wrapper()
                self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, py_name,
                                                                           "%s->obj" % py_name)
            else:
                wrapper.before_call.write_code("if (%s == NULL)\n{" % py_name)
                wrapper.before_call.indent()
                write_create_new_wrapper()
                self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, py_name,
                                                                           "%s->obj" % py_name)
                wrapper.before_call.unindent()
                wrapper.before_call.write_code('}')
            wrapper.build_params.add_parameter("N", [py_name])
        else:
            wrapper.before_call.write_code("if (typeid(*(%s)) == typeid(%s))\n{"
                                          % (value, self.cpp_class.helper_class.name))
            wrapper.before_call.indent()

            if self.type_traits.target_is_const:
                wrapper.before_call.write_code(
                    "%s = (%s*) (((%s*) ((%s*) %s))->m_pyself);"
                    % (py_name, self.cpp_class.pystruct,
                       self.cpp_class.helper_class.name, self.cpp_class.full_name, value))
                wrapper.before_call.write_code("%s->obj =  (%s*) (%s);" %
                                               (py_name, self.cpp_class.full_name, value))
            else:
                wrapper.before_call.write_code(
                    "%s = (%s*) (((%s*) %s)->m_pyself);"
                    % (py_name, self.cpp_class.pystruct,
                       self.cpp_class.helper_class.name, value))
                wrapper.before_call.write_code("%s->obj = %s;" % (py_name, value))
            wrapper.before_call.write_code("Py_INCREF(%s);" % py_name)
            wrapper.before_call.unindent()
            wrapper.before_call.write_code("} else {")
            wrapper.before_call.indent()

            try:
                self.cpp_class.wrapper_registry.write_lookup_wrapper(
                    wrapper.before_call, self.cpp_class.pystruct, py_name, value)
            except NotSupportedError:
                write_create_new_wrapper()
                self.cpp_class.wrapper_registry.write_register_new_wrapper(
                    wrapper.before_call, py_name, "%s->obj" % py_name)
            else:
                wrapper.before_call.write_code("if (%s == NULL)\n{" % py_name)
                wrapper.before_call.indent()
                write_create_new_wrapper()
                self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, py_name,
                                                                           "%s->obj" % py_name)
                wrapper.before_call.unindent()
                wrapper.before_call.write_code('}') # closes if (%s == NULL)

            wrapper.before_call.unindent()
            wrapper.before_call.write_code("}") # closes if (typeid(*(%s)) == typeid(%s))\n{
            wrapper.build_params.add_parameter("N", [py_name])
            


def _add_ward(wrapper, custodian, ward):
    wards = wrapper.declarations.declare_variable(
        'PyObject*', 'wards')
    wrapper.after_call.write_code(
        "%(wards)s = PyObject_GetAttrString(%(custodian)s, (char *) \"__wards__\");"
        % vars())
    wrapper.after_call.write_code(
        "if (%(wards)s == NULL) {\n"
        "    PyErr_Clear();\n"
        "    %(wards)s = PyList_New(0);\n"
        "    PyObject_SetAttrString(%(custodian)s, (char *) \"__wards__\", %(wards)s);\n"
        "}" % vars())
    wrapper.after_call.write_code("PyList_Append(%s, %s);" % (wards, ward))
    wrapper.after_call.add_cleanup_code("Py_DECREF(%s);" % wards)



class CppClassPtrReturnValue(CppClassReturnValueBase):
    "Class* return handler"
    CTYPES = []
    SUPPORTS_TRANSFORMATIONS = True
    cpp_class = cppclass.CppClass('dummy') # CppClass instance

    def __init__(self, ctype, caller_owns_return=None, custodian=None, is_const=False):
        """
        @param ctype: C type, normally 'MyClass*'
        @param caller_owns_return: if true, ownership of the object pointer
                              is transferred to the caller; should be
                              omitted when custodian is given.
        @param custodian: the object (custodian) that becomes responsible for
                     managing the life cycle of the return value.
                     Possible values are:
                       - None: no object is custodian;
                       - 0: the instance of the method in which
                     the ReturnValue is being used will become the
                     custodian;
                       - integer > 0: parameter number, starting at 1
                     (i.e. not counting the self/this parameter),
                     whose object will be used as custodian.
        @note: only arguments which are instances of C++ classes
        wrapped by PyBindGen can be used as custodians.
        """
        if ctype == self.cpp_class.name:
            ctype = self.cpp_class.full_name
        super(CppClassPtrReturnValue, self).__init__(ctype, is_const=is_const)
        if custodian is None:
            if caller_owns_return is None:
                if self.type_traits.target_is_const:
                    caller_owns_return = False
                else:
                    raise TypeConfigurationError("caller_owns_return not given")
        else:
            if caller_owns_return is not None:
                raise TypeConfigurationError("caller_owns_return should not given together with custodian")
        self.custodian = custodian
        if custodian is None:
            self.caller_owns_return = caller_owns_return
        else:
            assert caller_owns_return is None
            self.caller_owns_return = True

    def get_c_error_return(self): # only used in reverse wrappers
        """See ReturnValue.get_c_error_return"""
        return "return NULL;"

    def convert_c_to_python(self, wrapper):
        """See ReturnValue.convert_c_to_python"""

        ## Value transformations
        value = self.transformation.untransform(
            self, wrapper.declarations, wrapper.after_call, self.value)
        
        wrapper.after_call.write_code("if (!(%s)) {\n"
                                      "    Py_INCREF(Py_None);\n"
                                      "    return Py_None;\n"
                                      "}" % value)

        ## declare wrapper variable
        py_name = wrapper.declarations.declare_variable(
            self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
        self.py_name = py_name

        def write_create_new_wrapper():
            """Code path that creates a new wrapper for the returned object"""

            ## Find out what Python wrapper to use, in case
            ## automatic_type_narrowing is active and we are not forced to
            ## make a copy of the object
            if (self.cpp_class.automatic_type_narrowing
                and (self.caller_owns_return or isinstance(self.cpp_class.memory_policy,
                                                           cppclass.ReferenceCountingPolicy))):

                typeid_map_name = self.cpp_class.get_type_narrowing_root().typeid_map_name
                wrapper_type = wrapper.declarations.declare_variable(
                    'PyTypeObject*', 'wrapper_type', '0')
                wrapper.after_call.write_code(
                    '%s = %s.lookup_wrapper(typeid(*%s), &%s);'
                    % (wrapper_type, typeid_map_name, value, self.cpp_class.pytypestruct))

            else:

                wrapper_type = '&'+self.cpp_class.pytypestruct

            ## Create the Python wrapper object
            if self.cpp_class.allow_subclassing:
                new_func = 'PyObject_GC_New'
            else:
                new_func = 'PyObject_New'
            wrapper.after_call.write_code(
                "%s = %s(%s, %s);" %
                (py_name, new_func, self.cpp_class.pystruct, wrapper_type))
            self.py_name = py_name

            if self.cpp_class.allow_subclassing:
                wrapper.after_call.write_code(
                    "%s->inst_dict = NULL;" % (py_name,))

            ## Assign the C++ value to the Python wrapper
            if self.caller_owns_return:
                wrapper.after_call.write_code("%s->obj = %s;" % (py_name, value))
            else:
                if not isinstance(self.cpp_class.memory_policy, cppclass.ReferenceCountingPolicy):
                    ## The PyObject creates its own copy
                    self.cpp_class.write_create_instance(wrapper.after_call,
                                                         "%s->obj" % py_name,
                                                         '*'+value)
                else:
                    ## The PyObject gets a new reference to the same obj
                    self.cpp_class.memory_policy.write_incref(wrapper.after_call, value)
                    if self.type_traits.target_is_const:
                        wrapper.after_call.write_code("%s->obj = (%s*) (%s);" %
                                                      (py_name, self.cpp_class.full_name, value))
                    else:
                        wrapper.after_call.write_code("%s->obj = %s;" % (py_name, value))
        ## closes def write_create_new_wrapper():

        if self.cpp_class.helper_class is None:
            try:
                self.cpp_class.wrapper_registry.write_lookup_wrapper(
                    wrapper.after_call, self.cpp_class.pystruct, py_name, value)
            except NotSupportedError:
                write_create_new_wrapper()
                self.cpp_class.wrapper_registry.write_register_new_wrapper(
                    wrapper.after_call, py_name, "%s->obj" % py_name)
            else:
                wrapper.after_call.write_code("if (%s == NULL) {" % py_name)
                wrapper.after_call.indent()
                write_create_new_wrapper()
                self.cpp_class.wrapper_registry.write_register_new_wrapper(
                    wrapper.after_call, py_name, "%s->obj" % py_name)
                wrapper.after_call.unindent()

                # If we are already referencing the existing python wrapper,
                # we do not need a reference to the C++ object as well.
                if self.caller_owns_return and \
                        isinstance(self.cpp_class.memory_policy, cppclass.ReferenceCountingPolicy):
                    wrapper.after_call.write_code("} else {")
                    wrapper.after_call.indent()
                    self.cpp_class.memory_policy.write_decref(wrapper.after_call, value)
                    wrapper.after_call.unindent()
                    wrapper.after_call.write_code("}")
                else:
                    wrapper.after_call.write_code("}")            
        else:
            wrapper.after_call.write_code("if (typeid(*(%s)) == typeid(%s))\n{"
                                          % (value, self.cpp_class.helper_class.name))
            wrapper.after_call.indent()

            if self.type_traits.target_is_const:
                const_cast_value = "const_cast<%s *>(%s) " % (self.cpp_class.full_name, value)
            else:
                const_cast_value = value
            wrapper.after_call.write_code(
                "%s = reinterpret_cast< %s* >(reinterpret_cast< %s* >(%s)->m_pyself);"
                % (py_name, self.cpp_class.pystruct,
                   self.cpp_class.helper_class.name, const_cast_value))

            wrapper.after_call.write_code("%s->obj = %s;" % (py_name, const_cast_value))
        
            # We are already referencing the existing python wrapper,
            # so we do not need a reference to the C++ object as well.
            if self.caller_owns_return and \
                    isinstance(self.cpp_class.memory_policy, cppclass.ReferenceCountingPolicy):
                self.cpp_class.memory_policy.write_decref(wrapper.after_call, value)

            wrapper.after_call.write_code("Py_INCREF(%s);" % py_name)
            wrapper.after_call.unindent()
            wrapper.after_call.write_code("} else {") # if (typeid(*(%s)) == typeid(%s)) { ...
            wrapper.after_call.indent()

            # creater new wrapper or reference existing one
            try:
                self.cpp_class.wrapper_registry.write_lookup_wrapper(
                    wrapper.after_call, self.cpp_class.pystruct, py_name, value)
            except NotSupportedError:
                write_create_new_wrapper()
                self.cpp_class.wrapper_registry.write_register_new_wrapper(
                    wrapper.after_call, py_name, "%s->obj" % py_name)
            else:
                wrapper.after_call.write_code("if (%s == NULL) {" % py_name)
                wrapper.after_call.indent()
                write_create_new_wrapper()
                self.cpp_class.wrapper_registry.write_register_new_wrapper(
                    wrapper.after_call, py_name, "%s->obj" % py_name)
                wrapper.after_call.unindent()

                if self.caller_owns_return and \
                        isinstance(self.cpp_class.memory_policy, cppclass.ReferenceCountingPolicy):
                    wrapper.after_call.write_code("} else {")
                    wrapper.after_call.indent()
                    # If we are already referencing the existing python wrapper,
                    # we do not need a reference to the C++ object as well.
                    self.cpp_class.memory_policy.write_decref(wrapper.after_call, value)
                    wrapper.after_call.unindent()
                    wrapper.after_call.write_code("}")
                else:
                    wrapper.after_call.write_code("}")            


            wrapper.after_call.unindent()
            wrapper.after_call.write_code("}") # closes: if (typeid(*(%s)) == typeid(%s)) { ... } else { ...
            
        wrapper.build_params.add_parameter("N", [py_name], prepend=True)
        
        if self.custodian is None:
            pass
        elif self.custodian == 0:
            assert wrapper.class_.allow_subclassing
            _add_ward(wrapper, '((PyObject *) self)', "((PyObject *) %s)" % py_name)
        else:
            assert self.custodian > 0
            param = wrapper.parameters[self.custodian - 1]
            assert param.cpp_class.allow_subclassing
            assert isinstance(param, CppClassParameterBase)
            _add_ward(wrapper, "((PyObject *) %s)" % param.py_name, "((PyObject *) %s)" % py_name)
    

    def convert_python_to_c(self, wrapper):
        """See ReturnValue.convert_python_to_c"""
        name = wrapper.declarations.declare_variable(
            self.cpp_class.pystruct+'*', "tmp_%s" % self.cpp_class.name)
        wrapper.parse_params.add_parameter(
            'O!', ['&'+self.cpp_class.pytypestruct, '&'+name])

        value = self.transformation.transform(
            self, wrapper.declarations, wrapper.after_call, "%s->obj" % name)

        ## now the hairy part :)
        if self.caller_owns_return:
            if not isinstance(self.cpp_class.memory_policy, cppclass.ReferenceCountingPolicy):
                ## the caller receives a copy
                self.cpp_class.write_create_instance(wrapper.after_call,
                                                     "%s" % self.value,
                                                     '*'+value)
            else:
                ## the caller gets a new reference to the same obj
                self.cpp_class.memory_policy.write_incref(wrapper.after_call, value)
                if self.type_traits.target_is_const:
                    wrapper.after_call.write_code(
                        "%s = const_cast< %s* >(%s);" %
                        (self.value, self.cpp_class.full_name, value))
                else:
                    wrapper.after_call.write_code(
                        "%s = %s;" % (self.value, value))
        else:
            ## caller gets a shared pointer
            ## but this is dangerous, avoid at all cost!!!
            wrapper.after_call.write_code(
                "// dangerous!\n%s = %s;" % (self.value, value))
            warnings.warn("Returning shared pointers is dangerous!"
                          "  The C++ API should be redesigned "
                          "to avoid this situation.")
            


def implement_parameter_custodians(wrapper):
    """
    Generate the necessary code to implement the custodian=<N> option
    of C++ class parameters.  It accepts a Forward Wrapper as argument.
    """
    assert isinstance(wrapper, ForwardWrapperBase)
    for param in wrapper.parameters:
        if not isinstance(param, CppClassPtrParameter):
            continue
        if param.custodian is None:
            continue
        if param.custodian == -1: # the custodian is the return value
            assert wrapper.return_value.cpp_class.allow_subclassing
            _add_ward(wrapper, "((PyObject *) %s)" % wrapper.return_value.py_name,
                       "((PyObject *) %s)" % param.py_name)
        elif param.custodian == 0: # the custodian is the method self
            assert wrapper.class_.allow_subclassing
            _add_ward(wrapper, "((PyObject *) self)",
                       "((PyObject *) %s)" % param.py_name)
        else: # the custodian is another parameter
            assert param.custodian > 0
            custodian_param = wrapper.parameters[param.custodian - 1]
            assert custodian_param.cpp_class.allow_subclassing
            _add_ward(wrapper, "((PyObject *) %s)" % custodian_param.py_name,
                       "((PyObject *) %s)" % param.py_name)


