"""
Wrap C++ STL containers
"""

#import warnings

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

from pybindgen.typehandlers import codesink
from pybindgen.pytypeobject import PyTypeObject
from .typehandlers.ctypeparser import TypeTraits
from . import settings
from . import utils


class IterNextWrapper(ForwardWrapperBase):
    '''
    tp_iternext wrapper
    '''

    HAVE_RETURN_VALUE = True

    def __init__(self, container):
        """
        value_type -- a ReturnValue object handling the value type;
        container -- the L{Container}
        """
        super(IterNextWrapper, self).__init__(
            None, [], "return NULL;", "return NULL;", no_c_retval=True)
        assert isinstance(container, Container)
        self.container = container
        self.c_function_name = "_wrap_%s__tp_iternext" % (self.container.iter_pystruct)
        self.iter_variable_name = None
        self.reset_code_generation_state()
        #self.iter_variable_name = self.declarations.declare_variable(
        #    "%s::iterator" % container.full_name , 'iter')
        #self.container.value_type.value = "(*%s)" % self.iter_variable_name

    def reset_code_generation_state(self):
        super(IterNextWrapper, self).reset_code_generation_state()
        self.iter_variable_name = self.declarations.declare_variable(
            "%s::iterator" % self.container.full_name , 'iter')

    def generate_call(self):
        self.before_call.write_code("%s = *self->iterator;" % (self.iter_variable_name,))
        self.before_call.write_error_check(
            "%s == self->container->obj->end()" % (self.iter_variable_name,),
            "PyErr_SetNone(PyExc_StopIteration);")
        self.before_call.write_code("++(*self->iterator);")
        if self.container.key_type is None:
            self.container.value_type.value = "(*%s)" % self.iter_variable_name
            self.container.value_type.convert_c_to_python(self)
        else:
            self.container.value_type.value = "%s->second" % self.iter_variable_name
            self.container.value_type.convert_c_to_python(self)
            self.container.key_type.value = "%s->first" % self.iter_variable_name
            self.container.key_type.convert_c_to_python(self)

    def generate(self, code_sink):
        """
        code_sink -- a CodeSink instance that will receive the generated code
        """

        tmp_sink = codesink.MemoryCodeSink()
        self.generate_body(tmp_sink)
        code_sink.writeln("static PyObject* %s(%s *self)" % (self.c_function_name,
                                                             self.container.iter_pystruct))
        code_sink.writeln('{')
        code_sink.indent()
        tmp_sink.flush_to(code_sink)
        code_sink.unindent()
        code_sink.writeln('}')


class ContainerTraits(object):
    def __init__(self, add_value_method, is_mapping=False):
        self.add_value_method = add_value_method
        self.is_mapping = is_mapping

container_traits_list = {
    'list': 		ContainerTraits(add_value_method='push_back'),
    'deque': 		ContainerTraits(add_value_method='push_back'),
    'queue': 		ContainerTraits(add_value_method='push'),
    'priority_queue':	ContainerTraits(add_value_method='push'),
    'vector': 		ContainerTraits(add_value_method='push_back'),
    'stack': 		ContainerTraits(add_value_method='push'),
    'set': 		ContainerTraits(add_value_method='insert'),
    'multiset': 	ContainerTraits(add_value_method='insert'),
    'hash_set':		ContainerTraits(add_value_method='insert'),
    'hash_multiset':	ContainerTraits(add_value_method='insert'),
    'map':		ContainerTraits(add_value_method='insert', is_mapping=True),
}

# from wikipedia: """Deque is sometimes written dequeue, but this use
# is generally deprecated in technical literature or technical writing
# because dequeue is also a verb meaning "to remove from a queue" """.
container_traits_list['dequeue'] = container_traits_list['deque']

class Container(object):
    def __init__(self, name, value_type, container_type, outer_class=None, custom_name=None):
        """
        :param name: C++ type name of the container, e.g. std::vector<int> or MyIntList

        :param value_type: a ReturnValue of the element type: note,
            for mapping containers, value_type is a tuple with two
            ReturnValue's: (key, element).

        :param container_type: a string with the type of container,
            one of 'list', 'deque', 'queue', 'priority_queue',
            'vector', 'stack', 'set', 'multiset', 'hash_set',
            'hash_multiset', 'map'

        :param outer_class: if the type is defined inside a class, must be a reference to the outer class
        :type outer_class: None or L{CppClass}

        :param custom_name: alternative name to register with in the Python module

        """
        if '<' in name or '::' in name:
            self.name = utils.mangle_name(name)
            self.full_name = name
            self._full_name_is_definitive = True
        else:
            self._full_name_is_definitive = False
            self.full_name = None
            self.name = name
        self._module = None
        self.outer_class = outer_class
        self.mangled_name = None
        self.mangled_full_name = None
        self.container_traits = container_traits_list[container_type]
        self.custom_name = custom_name
        self._pystruct = None
        self.pytypestruct = "***GIVE ME A NAME***"
        self.pytype = PyTypeObject()

        self.iter_pytypestruct = "***GIVE ME A NAME***"
        self.iter_pytype = PyTypeObject()
        self._iter_pystruct = None

        if self.container_traits.is_mapping:
            (key_type, value_type) = value_type
            self.key_type = utils.eval_retval(key_type, self)
            self.value_type = utils.eval_retval(value_type, self)
        else:
            self.key_type = None
            self.value_type = utils.eval_retval(value_type, self)
        self.python_to_c_converter = None

        if name != 'dummy':
            ## register type handlers

            class ThisContainerParameter(ContainerParameter):
                """Register this C++ container as pass-by-value parameter"""
                CTYPES = []
                container_type = self
            self.ThisContainerParameter = ThisContainerParameter
            try:
                param_type_matcher.register(name, self.ThisContainerParameter)
            except ValueError:
                pass

            class ThisContainerRefParameter(ContainerRefParameter):
                """Register this C++ container as pass-by-value parameter"""
                CTYPES = []
                container_type = self
            self.ThisContainerRefParameter = ThisContainerRefParameter
            try:
                param_type_matcher.register(name + '&', self.ThisContainerRefParameter)
            except ValueError:
                pass

            class ThisContainerPtrParameter(ContainerPtrParameter):
                """Register this C++ container as pass-by-ptr parameter"""
                CTYPES = []
                container_type = self
            self.ThisContainerPtrParameter = ThisContainerPtrParameter
            try:
                param_type_matcher.register(name + '*', self.ThisContainerPtrParameter)
            except ValueError:
                pass

            class ThisContainerReturn(ContainerReturnValue):
                """Register this C++ container as value return"""
                CTYPES = []
                container_type = self
            self.ThisContainerReturn = ThisContainerReturn
            self.ThisContainerRefReturn = ThisContainerReturn
            try:
                return_type_matcher.register(name, self.ThisContainerReturn)
                return_type_matcher.register(name + '&', self.ThisContainerRefReturn)
            except ValueError:
                pass

    def __repr__(self):
        return "<pybindgen.Container %r>" % self.full_name

    def get_module(self):
        """Get the Module object this type belongs to"""
        return self._module

    def set_module(self, module):
        """Set the Module object this type belongs to"""
        self._module = module
        self._update_names()

    module = property(get_module, set_module)

    def get_pystruct(self):
        if self._pystruct is None:
            raise ValueError
        return self._pystruct
    pystruct = property(get_pystruct)

    def get_iter_pystruct(self):
        if self._iter_pystruct is None:
            raise ValueError
        return self._iter_pystruct
    iter_pystruct = property(get_iter_pystruct)

    def _update_names(self):

        prefix = settings.name_prefix.capitalize()

        if not self._full_name_is_definitive:
            if self.outer_class is None:
                if self._module.cpp_namespace_prefix:
                    if self._module.cpp_namespace_prefix == '::':
                        self.full_name = '::' + self.name
                    else:
                        self.full_name = self._module.cpp_namespace_prefix + '::' + self.name
                else:
                    self.full_name = self.name
            else:
                self.full_name = '::'.join([self.outer_class.full_name, self.name])

        def make_upper(s):
            if s and s[0].islower():
                return s[0].upper()+s[1:]
            else:
                return s

        def flatten(name):
            "make a name like::This look LikeThis"
            return ''.join([make_upper(utils.mangle_name(s)) for s in name.split('::')])

        self.mangled_name = flatten(self.name)
        self.mangled_full_name = utils.mangle_name(self.full_name)

        self._pystruct = "Py%s%s" % (prefix, self.mangled_full_name)
        self.pytypestruct = "Py%s%s_Type" % (prefix,  self.mangled_full_name)

        self._iter_pystruct = "Py%s%sIter" % (prefix, self.mangled_full_name)
        self.iter_pytypestruct = "Py%s%sIter_Type" % (prefix,  self.mangled_full_name)

        ## re-register the class type handlers, now with class full name
        self.register_alias(self.full_name)

        self.python_to_c_converter = self.module.get_root().get_python_to_c_type_converter_function_name(
            self.ThisContainerReturn(self.full_name))


    def register_alias(self, alias):
        """Re-register the class with another base name, in addition to any
        registrations that might have already been done."""

        self.module.register_type(None, alias, self)

        self.ThisContainerParameter.CTYPES.append(alias)
        try:
            param_type_matcher.register(alias, self.ThisContainerParameter)
        except ValueError: pass

        self.ThisContainerRefParameter.CTYPES.append(alias+'&')
        try:
            param_type_matcher.register(alias+'&', self.ThisContainerRefParameter)
        except ValueError: pass

        self.ThisContainerReturn.CTYPES.append(alias)
        try:
            return_type_matcher.register(alias, self.ThisContainerReturn)
        except ValueError: pass


    def generate_forward_declarations(self, code_sink, module):
        """
        Generates forward declarations for the instance and type
        structures.
        """

        # container pystruct
        code_sink.writeln('''
typedef struct {
    PyObject_HEAD
    %s *obj;
} %s;
    ''' % (self.full_name, self.pystruct))

        # container iterator pystruct
        code_sink.writeln('''
typedef struct {
    PyObject_HEAD
    %s *container;
    %s::iterator *iterator;
} %s;
    ''' % (self.pystruct, self.full_name, self.iter_pystruct))

        code_sink.writeln()
        code_sink.writeln('extern PyTypeObject %s;' % (self.pytypestruct,))
        code_sink.writeln('extern PyTypeObject %s;' % (self.iter_pytypestruct,))
        code_sink.writeln()

        this_type_converter = self.module.get_root().get_python_to_c_type_converter_function_name(
            self.ThisContainerReturn(self.full_name))
        self.module.get_root().declare_one_time_definition(this_type_converter)
        code_sink.writeln('int %(CONTAINER_CONVERTER_FUNC_NAME)s(PyObject *arg, %(CTYPE)s *container);'
                          % {'CTYPE': self.full_name,
                             'CONTAINER_CONVERTER_FUNC_NAME': this_type_converter,
                             })
        self.python_to_c_converter = this_type_converter

    def _get_python_name(self):
        if self.custom_name is None:
            class_python_name = self.mangled_name
        else:
            class_python_name = self.custom_name
        return class_python_name

    python_name = property(_get_python_name)

    def _get_python_full_name(self):
        if self.outer_class is None:
            mod_path = self._module.get_module_path()
            mod_path.append(self.python_name)
            return '.'.join(mod_path)
        else:
            return '%s.%s' % (self.outer_class.pytype.slots['tp_name'], self.python_name)

    python_full_name = property(_get_python_full_name)


    def generate(self, code_sink, module, docstring=None):
        """Generates the class to a code sink"""

        ## --- register the class type in the module ---
        module.after_init.write_code("/* Register the '%s' class */" % self.full_name)

        module.after_init.write_error_check('PyType_Ready(&%s)' % (self.pytypestruct,))
        module.after_init.write_error_check('PyType_Ready(&%s)' % (self.iter_pytypestruct,))

        class_python_name = self.python_name

        if self.outer_class is None:
            module.after_init.write_code(
                'PyModule_AddObject(m, (char *) \"%s\", (PyObject *) &%s);' % (
                class_python_name, self.pytypestruct))
            module.after_init.write_code(
                'PyModule_AddObject(m, (char *) \"%s\", (PyObject *) &%s);' % (
                class_python_name+'Iter', self.iter_pytypestruct))
        else:
            module.after_init.write_code(
                'PyDict_SetItemString((PyObject*) %s.tp_dict, (char *) \"%s\", (PyObject *) &%s);' % (
                self.outer_class.pytypestruct, class_python_name, self.pytypestruct))
            module.after_init.write_code(
                'PyDict_SetItemString((PyObject*) %s.tp_dict, (char *) \"%s\", (PyObject *) &%s);' % (
                self.outer_class.pytypestruct, class_python_name+'Iter', self.iter_pytypestruct))

        self._generate_gc_methods(code_sink)
        self._generate_destructor(code_sink)
        self._generate_iter_methods(code_sink)
        self._generate_container_constructor(code_sink)
        self._generate_type_structure(code_sink, docstring)

    def _generate_type_structure(self, code_sink, docstring):
        """generate the type structure"""

        self.pytype.slots.setdefault("tp_basicsize", "sizeof(%s)" % (self.pystruct,))
        self.pytype.slots.setdefault("tp_flags", "Py_TPFLAGS_DEFAULT")
        self.pytype.slots.setdefault("typestruct", self.pytypestruct)
        self.pytype.slots.setdefault("tp_name", self.python_full_name)
        self.pytype.generate(code_sink)

        self.iter_pytype.slots.setdefault("tp_basicsize", "sizeof(%s)" % (self.iter_pystruct,))
        self.iter_pytype.slots.setdefault("tp_flags", ("Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC"))
        self.iter_pytype.slots.setdefault("typestruct", self.iter_pytypestruct)
        self.iter_pytype.slots.setdefault("tp_name", self.python_full_name + 'Iter')
        self.iter_pytype.generate(code_sink)

    def _get_iter_delete_code(self):
        delete_code = ("delete self->iterator;\n"
                       "    self->iterator = NULL;\n")
        return delete_code

    def _get_container_delete_code(self):
        delete_code = ("delete self->obj;\n"
                       "    self->obj = NULL;\n")
        return delete_code

    def _generate_gc_methods(self, code_sink):
        """Generate tp_clear and tp_traverse"""

        ## --- iterator tp_clear ---
        tp_clear_function_name = "%s__tp_clear" % (self.iter_pystruct,)
        self.iter_pytype.slots.setdefault("tp_clear", tp_clear_function_name )

        code_sink.writeln(r'''
static void
%s(%s *self)
{
    Py_CLEAR(self->container);
    %s
}
''' % (tp_clear_function_name, self.iter_pystruct, self._get_iter_delete_code()))

        ## --- iterator tp_traverse ---
        tp_traverse_function_name = "%s__tp_traverse" % (self.iter_pystruct,)
        self.iter_pytype.slots.setdefault("tp_traverse", tp_traverse_function_name )

        code_sink.writeln(r'''
static int
%s(%s *self, visitproc visit, void *arg)
{
    Py_VISIT((PyObject *) self->container);
    return 0;
}
''' % (tp_traverse_function_name, self.iter_pystruct))


    def _generate_destructor(self, code_sink):
        """Generate a tp_dealloc function and register it in the type"""

        # -- container --
        container_tp_dealloc_function_name = "_wrap_%s__tp_dealloc" % (self.pystruct,)
        code_sink.writeln(r'''
static void
%s(%s *self)
{
    %s
    Py_TYPE(self)->tp_free((PyObject*)self);
}
''' % (container_tp_dealloc_function_name, self.pystruct,
       self._get_container_delete_code()))

        self.pytype.slots.setdefault("tp_dealloc", container_tp_dealloc_function_name )


        # -- iterator --
        iter_tp_dealloc_function_name = "_wrap_%s__tp_dealloc" % (self.iter_pystruct,)
        code_sink.writeln(r'''
static void
%s(%s *self)
{
    Py_CLEAR(self->container);
    %s
    Py_TYPE(self)->tp_free((PyObject*)self);
}
''' % (iter_tp_dealloc_function_name, self.iter_pystruct, self._get_iter_delete_code()))

        self.iter_pytype.slots.setdefault("tp_dealloc", iter_tp_dealloc_function_name )


    def _generate_iter_methods(self, code_sink):

        container_tp_iter_function_name = "_wrap_%s__tp_iter" % (self.pystruct,)
        iterator_tp_iter_function_name = "_wrap_%s__tp_iter" % (self.iter_pystruct,)
        subst_vars = {
            'CONTAINER_ITER_FUNC': container_tp_iter_function_name,
            'ITERATOR_ITER_FUNC': iterator_tp_iter_function_name,
            'PYSTRUCT': self.pystruct,
            'ITER_PYSTRUCT': self.iter_pystruct,
            'ITER_PYTYPESTRUCT': self.iter_pytypestruct,
            'CTYPE': self.full_name,
            }
        # -- container --
        code_sink.writeln(r'''
static PyObject*
%(CONTAINER_ITER_FUNC)s(%(PYSTRUCT)s *self)
{
    %(ITER_PYSTRUCT)s *iter = PyObject_GC_New(%(ITER_PYSTRUCT)s, &%(ITER_PYTYPESTRUCT)s);
    Py_INCREF(self);
    iter->container = self;
    iter->iterator = new %(CTYPE)s::iterator(self->obj->begin());
    return (PyObject*) iter;
}
''' % subst_vars)

        self.pytype.slots.setdefault("tp_iter", container_tp_iter_function_name)


        # -- iterator --
        container_tp_iter_function_name = "_wrap_%s__tp_iter" % (self.pystruct,)
        code_sink.writeln(r'''
static PyObject*
%(ITERATOR_ITER_FUNC)s(%(ITER_PYSTRUCT)s *self)
{
    Py_INCREF(self);
    return (PyObject*) self;
}
''' % subst_vars)

        self.iter_pytype.slots.setdefault("tp_iter", iterator_tp_iter_function_name)

        # -- iterator tp_iternext
        iternext = IterNextWrapper(self)
        iternext.generate(code_sink)
        self.iter_pytype.slots.setdefault("tp_iternext", iternext.c_function_name)



    def _generate_container_constructor(self, code_sink):
        container_tp_init_function_name = "_wrap_%s__tp_init" % (self.pystruct,)
        item_python_to_c_converter = self.module.get_root().generate_python_to_c_type_converter(self.value_type, code_sink)
        if self.key_type is not None:
            key_python_to_c_converter = self.module.get_root().generate_python_to_c_type_converter(self.key_type, code_sink)
        this_type_converter = self.module.get_root().get_python_to_c_type_converter_function_name(
            self.ThisContainerReturn(self.full_name))
        subst_vars = {
            'FUNC': container_tp_init_function_name,
            'PYSTRUCT': self.pystruct,
            'PYTYPESTRUCT': self.pytypestruct,
            'CTYPE': self.full_name,
            'ITEM_CONVERTER': item_python_to_c_converter,
            'PYTHON_NAME': self.python_name,
            'ITEM_CTYPE': self.value_type.ctype,
            'CONTAINER_CONVERTER_FUNC_NAME': this_type_converter,
            'ADD_VALUE': self.container_traits.add_value_method,
            }

        if self.key_type is None:

            # generate mapping converter function

            code_sink.writeln(r'''
int %(CONTAINER_CONVERTER_FUNC_NAME)s(PyObject *arg, %(CTYPE)s *container)
{
    if (PyObject_IsInstance(arg, (PyObject*) &%(PYTYPESTRUCT)s)) {
        *container = *((%(PYSTRUCT)s*)arg)->obj;
    } else if (PyList_Check(arg)) {
        container->clear();
        Py_ssize_t size = PyList_Size(arg);
        for (Py_ssize_t i = 0; i < size; i++) {
            %(ITEM_CTYPE)s item;
            if (!%(ITEM_CONVERTER)s(PyList_GET_ITEM(arg, i), &item)) {
                return 0;
            }
            container->%(ADD_VALUE)s(item);
        }
    } else {
        PyErr_SetString(PyExc_TypeError, "parameter must be None, a %(PYTHON_NAME)s instance, or a list of %(ITEM_CTYPE)s");
        return 0;
    }
    return 1;
}
''' % subst_vars)

        else:

            # generate mapping converter function

            subst_vars.update({
                    'KEY_CONVERTER': key_python_to_c_converter,
                    'KEY_CTYPE': self.key_type.ctype,
                    })

            code_sink.writeln(r'''
int %(CONTAINER_CONVERTER_FUNC_NAME)s(PyObject *arg, %(CTYPE)s *container)
{
    if (PyObject_IsInstance(arg, (PyObject*) &%(PYTYPESTRUCT)s)) {
        *container = *((%(PYSTRUCT)s*)arg)->obj;
    } else if (PyList_Check(arg)) {
        container->clear();
        Py_ssize_t size = PyList_Size(arg);
        for (Py_ssize_t i = 0; i < size; i++) {
            PyObject *tup = PyList_GET_ITEM(arg, i);
            if (!PyTuple_Check(tup) || PyTuple_Size(tup) != 2) {
                PyErr_SetString(PyExc_TypeError, "items must be tuples with two elements");
                return 0;
            }
            std::pair< %(KEY_CTYPE)s, %(ITEM_CTYPE)s > item;
            if (!%(KEY_CONVERTER)s(PyTuple_GET_ITEM(tup, 0), &item.first)) {
                return 0;
            }
            if (!%(ITEM_CONVERTER)s(PyTuple_GET_ITEM(tup, 1), &item.second)) {
                return 0;
            }
            container->%(ADD_VALUE)s(item);
        }
    } else {
        PyErr_SetString(PyExc_TypeError, "parameter must be None, a %(PYTHON_NAME)s instance, or a list of %(ITEM_CTYPE)s");
        return 0;
    }
    return 1;
}
''' % subst_vars)
            # ---

        # constructor, calling the above converter function
        code_sink.writeln(r'''
static int
%(FUNC)s(%(PYSTRUCT)s *self, PyObject *args, PyObject *kwargs)
{
    const char *keywords[] = {"arg", NULL};
    PyObject *arg = NULL;

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, (char *) "|O", (char **) keywords, &arg)) {
        return -1;
    }

    self->obj = new %(CTYPE)s;

    if (arg == NULL)
        return 0;

    if (!%(CONTAINER_CONVERTER_FUNC_NAME)s(arg, self->obj)) {
        delete self->obj;
        self->obj = NULL;
        return -1;
    }
    return 0;
}
''' % subst_vars)

        self.pytype.slots.setdefault("tp_init", container_tp_init_function_name)



## ----------------------------
## Type Handlers
## ----------------------------

def _get_dummy_container():
    return Container('dummy', ReturnValue.new('void'), 'list') # Container instance

class ContainerParameterBase(Parameter):
    "Base class for all C++ Class parameter handlers"
    CTYPES = []
    container_type = _get_dummy_container()
    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.container_type.name:
            ctype = self.container_type.full_name
        super(ContainerParameterBase, self).__init__(
            ctype, name, direction, is_const, default_value)

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


class ContainerReturnValueBase(ReturnValue):
    "Class return handlers -- base class"
    CTYPES = []
    container_type = _get_dummy_container()

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




class ContainerParameter(ContainerParameterBase):
    "Container handlers"
    CTYPES = []
    container_type = _get_dummy_container()
    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.container_type, Container)

        assert self.default_value is None, "default value not implemented for containers"

        #self.py_name = wrapper.declarations.declare_variable('PyObject*', self.name)
        container_tmp_var = wrapper.declarations.declare_variable(
            self.container_type.full_name, self.name + '_value')
        wrapper.parse_params.add_parameter('O&', [self.container_type.python_to_c_converter, '&'+container_tmp_var], self.name)
        wrapper.call_params.append(container_tmp_var)

    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.container_type.pystruct+'*', 'py_'+self.container_type.name)
        wrapper.before_call.write_code(
            "%s = PyObject_New(%s, %s);" %
            (self.py_name, self.container_type.pystruct, '&'+self.container_type.pytypestruct))

        wrapper.before_call.write_code("%s->obj = new %s(%s);" % (self.py_name, self.container_type.full_name, self.value))

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


class ContainerRefParameter(ContainerParameterBase):
    "Container handlers"
    CTYPES = []
    container_type = _get_dummy_container()
    DIRECTIONS = [Parameter.DIRECTION_IN, Parameter.DIRECTION_OUT, Parameter.DIRECTION_INOUT]

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

        #assert self.default_value is None, "default value not implemented for containers"

        #self.py_name = wrapper.declarations.declare_variable('PyObject*', self.name)
        container_tmp_var = wrapper.declarations.declare_variable(
            self.container_type.full_name, self.name + '_value', self.default_value)

        if self.direction & Parameter.DIRECTION_IN:
            wrapper.parse_params.add_parameter('O&', [self.container_type.python_to_c_converter, '&'+container_tmp_var], self.name,
                                               optional=(self.default_value is not None))

        wrapper.call_params.append(container_tmp_var)

        if self.direction & Parameter.DIRECTION_OUT:
            py_name = wrapper.declarations.declare_variable(
                self.container_type.pystruct+'*', 'py_'+self.container_type.name)
            wrapper.after_call.write_code(
                "%s = PyObject_New(%s, %s);" %
                (py_name, self.container_type.pystruct, '&'+self.container_type.pytypestruct))
            wrapper.after_call.write_code("%s->obj = new %s(%s);" % (py_name, self.container_type.full_name, container_tmp_var))
            wrapper.build_params.add_parameter("N", [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.container_type.pystruct+'*', 'py_'+self.container_type.name)
        wrapper.before_call.write_code(
            "%s = PyObject_New(%s, %s);" %
            (self.py_name, self.container_type.pystruct, '&'+self.container_type.pytypestruct))

        if self.direction & Parameter.DIRECTION_IN:
            wrapper.before_call.write_code("%s->obj = new %s(%s);" % (self.py_name, self.container_type.full_name, self.name))
        else:
            wrapper.before_call.write_code("%s->obj = new %s;" % (self.py_name, self.container_type.full_name))

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

        if self.direction & Parameter.DIRECTION_OUT:
            wrapper.parse_params.add_parameter('O&', [self.container_type.python_to_c_converter, '&'+self.name], self.name)


class ContainerPtrParameter(ContainerParameterBase):
    "Container handlers"
    CTYPES = []
    container_type = _get_dummy_container()
    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, transfer_ownership=None):
        super(ContainerPtrParameter, self).__init__(ctype, name, direction, is_const, default_value)

        if self.direction == Parameter.DIRECTION_OUT:
            if transfer_ownership is not None and not transfer_ownership:
                raise TypeConfigurationError("with direction=out, transfer_ownership must be True or omitted (got %r)"
                                             % transfer_ownership)
            self.transfer_ownership = True
        else:
            if transfer_ownership is None:
                raise TypeConfigurationError("transfer_ownership parameter was not given")
            self.transfer_ownership = transfer_ownership

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

        assert self.default_value is None, "default value not implemented for containers"


        if self.direction == Parameter.DIRECTION_IN:
            container_tmp_var = wrapper.declarations.declare_variable(
                self.container_type.full_name, self.name + '_value')
            wrapper.parse_params.add_parameter('O&', [self.container_type.python_to_c_converter, '&'+container_tmp_var], self.name)
            if self.transfer_ownership:
                wrapper.call_params.append("new %s(%s)" % (self.container_type.full_name, container_tmp_var))
            else:
                wrapper.call_params.append("&%s" % container_tmp_var)

        elif self.direction == Parameter.DIRECTION_OUT:
            container_tmp_var = wrapper.declarations.declare_variable(
                self.container_type.full_name + '*', self.name + '_value', initializer="new %s" % self.container_type.full_name)

            wrapper.call_params.append(container_tmp_var)

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

            wrapper.after_call.write_code(
                "%s = PyObject_New(%s, %s);" %
                (py_name, self.container_type.pystruct, '&'+self.container_type.pytypestruct))

            wrapper.after_call.write_code("%s->obj = %s;" % (py_name, container_tmp_var))

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

        else:
            raise NotSupportedError("inout not supported for container*")


    def convert_c_to_python(self, wrapper):
        '''Write some code before calling the Python method.'''
        raise NotSupportedError("container* reverse type handler not yet implemeneted")


class ContainerReturnValue(ContainerReturnValueBase):
    "Container type return handlers"
    CTYPES = []

    NO_RETVAL_DECL = True
    container_type = _get_dummy_container()

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

        super(ContainerReturnValue, self).__init__(ctype)
        self.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.container_type.full_name,)

    def _get_noref_ctype(self):
        # if the type has foo const &, get back foo
        traits = TypeTraits(str(self.type_traits.ctype_no_const_no_ref))
        ctype_no_const_no_ref = traits.ctype_no_const_no_ref
        return ctype_no_const_no_ref

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

        ctype_no_const_no_ref = self._get_noref_ctype()
        retval = wrapper.declarations.declare_variable(
            str(ctype_no_const_no_ref), 'retval')
        assert retval == 'retval'

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

        self.py_name = py_name

        wrapper.after_call.write_code(
            "%s = PyObject_New(%s, %s);" %
            (py_name, self.container_type.pystruct, '&'+self.container_type.pytypestruct))
        wrapper.after_call.write_code("%s->obj = new %s(%s);" % (self.py_name, self.container_type.full_name, self.value))
        wrapper.build_params.add_parameter("N", [py_name], prepend=True)

    def convert_python_to_c(self, wrapper):
        """see ReturnValue.convert_python_to_c"""
        if '&' in self.ctype:
            raise NotSupportedError("reference return type not supported")
        retval = wrapper.declarations.declare_variable(self.ctype, 'retval')
        assert retval == 'retval'
        wrapper.parse_params.add_parameter('O&', [self.container_type.python_to_c_converter, '&'+self.value])


## end
