from contextlib import contextmanager
import os.path
import sys
import re
from collections import defaultdict
 
from ConversionProvider import setup_converter_registry
from DeclResolver import (ResolvedClass, ResolvedEnum, ResolvedTypeDef,
                          ResolvedFunction)
 
from Types import CppType  # , printable
 
import Code
 
import logging as L
 
 
@contextmanager
def stdout_redirect(stream):
    sys.stdout = stream
    yield
    sys.stdout = sys.__stdout__
 
 
def augment_arg_names(method):
    """ replaces missing arg_names with "in_%d" % i, where i is the position
        number of the arg """
    return [(t, n if (n and n != "self") else "in_%d" % i)
            for i, (n, t) in enumerate(method.arguments)]
 
 
def fixed_include_dirs():
    import pkg_resources
    boost = pkg_resources.resource_filename("autowrap", "data_files/boost")
    data = pkg_resources.resource_filename("autowrap", "data_files")
    return [boost, data]
 
 
class CodeGenerator(object):
 
    """
    This is the main Code Generator.
 
    Its main entry function is "create_pyx_file" which generates the pyx file
    from the input (given in the initializiation).
 
    The actual conversion of input/output arguments is done in the
    ConversionProviders for each argument type.
    """
    def __init__(self, resolved, instance_mapping, target_path=None,
                 manual_code=None, extra_cimports=None):
 
        if manual_code is None:
            manual_code = dict()
 
        self.manual_code = manual_code
        self.extra_cimports = extra_cimports
 
        self.target_path = os.path.abspath(target_path)
        self.target_dir = os.path.dirname(self.target_path)
 
        self.classes = [d for d in resolved if isinstance(d, ResolvedClass)]
        self.enums = [d for d in resolved if isinstance(d, ResolvedEnum)]
        self.functions = [d for d in resolved if isinstance(d, ResolvedFunction)]
        self.typedefs = [d for d in resolved if isinstance(d, ResolvedTypeDef)]
 
        self.resolved = []
        self.resolved.extend(sorted(self.typedefs, key=lambda d: d.name))
        self.resolved.extend(sorted(self.enums, key=lambda d: d.name))
        self.resolved.extend(sorted(self.functions, key=lambda d: d.name))
        self.resolved.extend(sorted(self.classes, key=lambda d: d.name))
 
        self.instance_mapping = instance_mapping
 
        self.cr = setup_converter_registry(self.classes, self.enums, instance_mapping)
 
        self.top_level_code = []
        self.class_codes = defaultdict(list)
        self.wrapped_enums_cnt = 0
        self.wrapped_classes_cnt = 0
        self.wrapped_methods_cnt = 0
 
    def get_include_dirs(self):
        if self.pxd_dir is not None:
            return fixed_include_dirs() + [self.pxd_dir]
        else:
            return fixed_include_dirs()
 
    def setup_cimport_paths(self):
 
        pxd_dirs = set()
        for inst in self.classes + self.enums + self.functions + self.typedefs:
            pxd_path = os.path.abspath(inst.cpp_decl.pxd_path)
            pxd_dir = os.path.dirname(pxd_path)
            pxd_dirs.add(pxd_dir)
            pxd_file = os.path.basename(pxd_path)
            inst.pxd_import_path, __ = os.path.splitext(pxd_file)
 
        assert len(pxd_dirs) <= 1, "pxd files must be located in same directory"
 
        self.pxd_dir = pxd_dirs.pop() if pxd_dirs else None
 
    def create_pyx_file(self, debug=False):
        """This creates the actual Cython code
 
        It calls create_wrapper_for_class, create_wrapper_for_enum and
        create_wrapper_for_free_function respectively to create the code for
        all classes, enums and free functions.
        """
        self.setup_cimport_paths()
        self.create_cimports()
        self.create_includes()
 
        def create_for(clz, method):
            for resolved in self.resolved:
                if resolved.wrap_ignore:
                    continue
                if isinstance(resolved, clz):
                    method(resolved)
 
        # first wrap classes, so that self.class_codes[..] is initialized
        # for attaching enums or static functions
        create_for(ResolvedClass, self.create_wrapper_for_class)
        create_for(ResolvedEnum, self.create_wrapper_for_enum)
        create_for(ResolvedFunction, self.create_wrapper_for_free_function)
 
        code = "\n".join(ci.render() for ci in self.top_level_code)
        code += " \n"
        names = set()
        for n, c in self.class_codes.items():
            code += c.render()
            code += " \n"
            names.add(n)
 
        # manual code which does not extend wrapped classes:
        for name, c in self.manual_code.items():
            if name not in names:
                code += c.render()
            code += " \n"
 
        if debug:
            print code
        with open(self.target_path, "w") as fp:
            print >> fp, code
 
    def filterout_iterators(self, methods):
        def parse(anno):
            m = re.match("(\S+)\((\S+)\)", anno)
            assert m is not None, "invalid iter annotation"
            name, type_str = m.groups()
            return name, CppType.from_string(type_str)
 
        begin_iterators = dict()
        end_iterators = dict()
        non_iter_methods = defaultdict(list)
        for name, mi in methods.items():
            for method in mi:
                annotations = method.cpp_decl.annotations
                if "wrap-iter-begin" in annotations:
                    py_name, res_type = parse(annotations["wrap-iter-begin"])
                    begin_iterators[py_name] = (method, res_type)
                elif "wrap-iter-end" in annotations:
                    py_name, res_type = parse(annotations["wrap-iter-end"])
                    end_iterators[py_name] = (method, res_type)
                else:
                    non_iter_methods[name].append(method)
 
        begin_names = set(begin_iterators.keys())
        end_names = set(end_iterators.keys())
        common_names = begin_names & end_names
        if begin_names != end_names:
            # TODO: diesen fall testen
            raise Exception("iter declarations not balanced")
 
        for py_name in common_names:
            __, res_type_begin = begin_iterators[py_name]
            __, res_type_end = end_iterators[py_name]
            assert res_type_begin == res_type_end, "iter value types do not match"
 
        begin_methods = dict((n, m) for n, (m, __) in begin_iterators.items())
        end_methods = dict((n, m) for n, (m, __) in end_iterators.items())
        res_types = dict((n, t) for n, (__, t) in end_iterators.items())
 
        iterators = dict()
        for n in common_names:
            iterators[n] = (begin_methods[n], end_methods[n], res_types[n])
 
        return iterators, non_iter_methods
 
    def create_wrapper_for_enum(self, decl):
        self.wrapped_enums_cnt += 1
        if decl.cpp_decl.annotations.get("wrap-attach"):
            name = "__" + decl.name
        else:
            name = decl.name
        L.info("create wrapper for enum %s" % name)
        code = Code.Code()
        code.add("""
                   |
                   |cdef class $name:
                 """, name=name)
        for (name, value) in decl.items:
            code.add("    $name = $value", name=name, value=value)
        self.class_codes[decl.name] = code
 
        for class_name in decl.cpp_decl.annotations.get("wrap-attach", []):
            code = Code.Code()
            code.add("%s = %s" % (decl.name, "__" + decl.name))
            self.class_codes[class_name].add(code)
 
    def create_wrapper_for_class(self, r_class):
        """Create Cython code for a single class"""
        self.wrapped_classes_cnt += 1
        self.wrapped_methods_cnt += len(r_class.methods)
        cname = r_class.name
        L.info("create wrapper for class %s" % cname)
        cy_type = self.cr.cython_type(cname)
        class_code = Code.Code()
        if r_class.methods:
            class_code.add("""
                            |
                            |cdef class $cname:
                            |
                            |    cdef shared_ptr[$cy_type] inst
                            |
                            |    def __dealloc__(self):
                            |         self.inst.reset()
                            |
                            """, locals())
        else:
            class_code.add("""
                            |
                            |cdef class $cname:
                            |
                            """, locals())
 
        self.class_codes[cname] = class_code
 
        cons_created = False
 
        for attribute in r_class.attributes:
            if not attribute.wrap_ignore:
                class_code.add(self._create_wrapper_for_attribute(attribute))
 
        iterators, non_iter_methods = self.filterout_iterators(r_class.methods)
 
        for (name, methods) in non_iter_methods.items():
            if name == r_class.name:
                codes = self.create_wrapper_for_constructor(r_class, methods)
                cons_created = True
            else:
                codes = self.create_wrapper_for_method(r_class, name, methods)
 
            for ci in codes:
                class_code.add(ci)
 
        has_ops = dict()
        for ops in ["==", "!=", "<", "<=", ">", ">="]:
            has_op = ("operator%s" % ops) in non_iter_methods
            has_ops[ops] = has_op
 
        if any(v for v in has_ops.values()):
            code = self.create_special_cmp_method(r_class, has_ops)
            class_code.add(code)
 
        codes = self._create_iter_methods(iterators, r_class.instance_map, r_class.local_map)
        for ci in codes:
            class_code.add(ci)
 
        extra_methods_code = self.manual_code.get(cname)
        if extra_methods_code:
            class_code.add(extra_methods_code)
 
    def _create_iter_methods(self, iterators, instance_mapping, local_mapping):
        codes = []
        for name, (begin_decl, end_decl, res_type) in iterators.items():
            L.info("   create wrapper for iter %s" % name)
            meth_code = Code.Code()
            begin_name = begin_decl.name
            end_name = end_decl.name
 
            # TODO: this step is dupblicated from DeclResolver.py
            # can we cobine botho maps to one single map ?
            res_type = res_type.transformed(local_mapping)
            res_type = res_type.inv_transformed(instance_mapping)
 
            cy_type = self.cr.cython_type(res_type)
            base_type = res_type.base_type
 
            meth_code.add("""
                            |
                            |def $name(self):
                            |    it = self.inst.get().$begin_name()
                            |    cdef $base_type out
                            |    while it != self.inst.get().$end_name():
                            |        out = $base_type.__new__($base_type)
                            |        out.inst =
                            + shared_ptr[$cy_type](new $cy_type(deref(it)))
                            |        yield out
                            |        inc(it)
                            """, locals())
 
            codes.append(meth_code)
        return codes
 
    def _create_overloaded_method_decl(self, py_name, dispatched_m_names, methods, use_return):
 
        L.info("   create wrapper decl for overloaded method %s" % py_name)
 
        method_code = Code.Code()
        method_code.add("""
                          |
                          |def $py_name(self, *args):
                        """, locals())
 
        first_iteration = True
        for (dispatched_m_name, method) in zip(dispatched_m_names, methods):
            args = augment_arg_names(method)
            if not args:
                check_expr = "not args"
            else:
                tns = [(t, "args[%d]" % i) for i, (t, n) in enumerate(args)]
                checks = ["len(args)==%d" % len(tns)]
                checks += [self.cr.get(t).type_check_expression(t, n) for (t, n) in tns]
                check_expr = " and ".join("(%s)" % c for c in checks)
            return_ = "return" if use_return else ""
            if_elif = "if" if first_iteration else "elif"
            method_code.add("""
                            |    $if_elif $check_expr:
                            |        $return_ self.$dispatched_m_name(*args)
                            """, locals())
            first_iteration = False
 
        method_code.add("""    else:
                        |           raise
                        + Exception('can not handle type of %s' % (args,))""")
        return method_code
 
    def create_wrapper_for_method(self, cdcl, py_name, methods):
 
        if py_name.startswith("operator"):
            __, __, op = py_name.partition("operator")
            if op in ["!=", "==", "<", "<=", ">", ">="]:
                # handled in create_wrapper_for_class, as one has to collect
                # these
                return []
            elif op == "()":
                codes = self.create_cast_methods(methods)
                return codes
            elif op == "[]":
                assert len(methods) == 1, "overloaded operator[] not suppored"
                code = self.create_special_getitem_method(methods[0])
                return [code]
            elif op == "+":
                assert len(methods) == 1, "overloaded operator+ not suppored"
                code = self.create_special_add_method(cdcl, methods[0])
                return [code]
            elif op == "+=":
                assert len(methods) == 1, "overloaded operator+= not suppored"
                code = self.create_special_iadd_method(cdcl, methods[0])
                return [code]
 
        if len(methods) == 1:
            code = self.create_wrapper_for_nonoverloaded_method(cdcl, py_name, methods[0])
            return [code]
        else:
            # TODO: what happens if two distinct c++ types as float, double
            # map to the same python type ??
            # -> 1) detection
            # -> 2) force method renaming
            codes = []
            dispatched_m_names = []
            for (i, method) in enumerate(methods):
                dispatched_m_name = "_%s_%d" % (py_name, i)
                dispatched_m_names.append(dispatched_m_name)
                code = self.create_wrapper_for_nonoverloaded_method(cdcl,
                                                                    dispatched_m_name,
                                                                    method,
                                                                    )
                codes.append(code)
 
            code = self._create_overloaded_method_decl(py_name, dispatched_m_names, methods, True)
            codes.append(code)
            return codes
 
    def _create_fun_decl_and_input_conversion(self, code, py_name, method, is_free_fun=False):
        """ Creates the function declarations and the input conversion to C++
        and the output conversion back to Python.
 
        The input conversion is directly added to the "code" object while the
        conversion back to Python is returned as "cleanups".
        """
        args = augment_arg_names(method)
 
        # Step 0: collect conversion data for input args and call
        # input_conversion for more sophisticated conversion code (e.g.
        # std::vector<Obj>)
        py_signature_parts = []
        input_conversion_codes = []
        cleanups = []
        call_args = []
        checks = []
        in_types = []
        for arg_num, (t, n) in enumerate(args):
            # get new ConversionProvider using the converter registry
            converter = self.cr.get(t)
            converter.cr = self.cr
            py_type = converter.matching_python_type(t)
            conv_code, call_as, cleanup = converter.input_conversion(t, n, arg_num)
            py_signature_parts.append("%s %s " % (py_type, n))
            input_conversion_codes.append(conv_code)
            cleanups.append(cleanup)
            call_args.append(call_as)
            in_types.append(t)
            checks.append((n, converter.type_check_expression(t, n)))
 
        # Step 1: create method decl statement
        if not is_free_fun:
            py_signature_parts.insert(0, "self")
        py_signature = ", ".join(py_signature_parts)
        code.add("""
                   |
                   |def $py_name($py_signature):
                   """, locals())
 
        # Step 2a: create code which convert python input args to c++ args of
        # wrapped method
        for n, check in checks:
            code.add("    assert %s, 'arg %s wrong type'" % (check, n))
        # Step 2b: add any more sophisticated conversion code that was created
        # above:
        for conv_code in input_conversion_codes:
            code.add(conv_code)
 
        return call_args, cleanups, in_types
 
    def _create_wrapper_for_attribute(self, attribute):
        code = Code.Code()
        name = attribute.name
        wrap_as = attribute.cpp_decl.annotations.get("wrap-as", name)
 
        t = attribute.type_
 
        converter = self.cr.get(t)
        py_type = converter.matching_python_type(t)
        conv_code, call_as, cleanup = converter.input_conversion(t, name, 0)
 
        code.add("""
            |
            |property $wrap_as:
            |
            |    def __set__(self, $py_type $name):
            """, locals())
 
        # TODO: add mit indent level
        indented = Code.Code()
        indented.add(conv_code)
        code.add(indented)
 
        code.add("""
            |        self.inst.get().$name = $call_as
            """, locals())
        indented = Code.Code()
        if isinstance(cleanup, basestring):
            cleanup = "    %s" % cleanup
        indented.add(cleanup)
        code.add(indented)
 
        to_py_code = converter.output_conversion(t, "_r", "py_result")
        access_stmt = converter.call_method(t, "self.inst.get().%s" % name)
 
        cy_type = self.cr.cython_type(t)
 
        if isinstance(to_py_code, basestring):
            to_py_code = "    %s" % to_py_code
 
        if t.is_ptr:
            # For pointer types, we need to guard against unsafe access
            code.add("""
                |
                |    def __get__(self):
                |        if self.inst.get().%s is NULL:
                |             raise Exception("Cannot access pointer that is NULL")
                |        $access_stmt
                """ % name, locals())
        else:
            code.add("""
                |
                |    def __get__(self):
                |        $access_stmt
                """, locals())
        # increase indent:
        indented = Code.Code()
        indented.add(to_py_code)
        code.add(indented)
        code.add("        return py_result")
        return code
 
    def create_wrapper_for_nonoverloaded_method(self, cdcl, py_name, method):
 
        L.info("   create wrapper for %s ('%s')" % (py_name, method))
        meth_code = Code.Code()
 
        call_args, cleanups, in_types = self._create_fun_decl_and_input_conversion(meth_code,
                                                                                   py_name,
                                                                                   method)
 
        # call wrapped method and convert result value back to python
        cpp_name = method.cpp_decl.name
        call_args_str = ", ".join(call_args)
        cy_call_str = "self.inst.get().%s(%s)" % (cpp_name, call_args_str)
 
        res_t = method.result_type
        out_converter = self.cr.get(res_t)
        full_call_stmt = out_converter.call_method(res_t, cy_call_str)
 
        if isinstance(full_call_stmt, basestring):
            meth_code.add("""
                |    $full_call_stmt
                """, locals())
        else:
            meth_code.add(full_call_stmt)
 
        for cleanup in reversed(cleanups):
            if not cleanup:
                continue
            if isinstance(cleanup, basestring):
                cleanup = "    %s" % cleanup
            meth_code.add(cleanup)
 
        to_py_code = out_converter.output_conversion(res_t, "_r", "py_result")
 
        if to_py_code is not None:  # for non void return value
 
            if isinstance(to_py_code, basestring):
                to_py_code = "    %s" % to_py_code
            meth_code.add(to_py_code)
            meth_code.add("    return py_result")
 
        return meth_code
 
    def create_wrapper_for_free_function(self, decl):
        L.info("create wrapper for free function %s" % decl.name)
        self.wrapped_methods_cnt += 1
        static_clz = decl.cpp_decl.annotations.get("wrap-attach")
        if static_clz is None:
            code = self._create_wrapper_for_free_function(decl)
        else:
            code = Code.Code()
            static_name = "__static_%s_%s" % (static_clz, decl.name)
            code.add("%s = %s" % (decl.name, static_name))
            self.class_codes[static_clz].add(code)
            code = self._create_wrapper_for_free_function(decl, static_name)
 
        self.top_level_code.append(code)
 
    def _create_wrapper_for_free_function(self, decl, name=None):
        if name is None:
            name = decl.name
 
        fun_code = Code.Code()
 
        call_args, cleanups, in_types =\
            self._create_fun_decl_and_input_conversion(fun_code, name, decl, is_free_fun=True)
 
        call_args_str = ", ".join(call_args)
        mangled_name = "_" + decl.name + "_" + decl.pxd_import_path
        cy_call_str = "%s(%s)" % (mangled_name, call_args_str)
 
        res_t = decl.result_type
        out_converter = self.cr.get(res_t)
        full_call_stmt = out_converter.call_method(res_t, cy_call_str)
 
        if isinstance(full_call_stmt, basestring):
            fun_code.add("""
                |    $full_call_stmt
                """, locals())
        else:
            fun_code.add(full_call_stmt)
 
        for cleanup in reversed(cleanups):
            if not cleanup:
                continue
            if isinstance(cleanup, basestring):
                cleanup = "    %s" % cleanup
            fun_code.add(cleanup)
 
        to_py_code = out_converter.output_conversion(res_t, "_r", "py_result")
 
        out_vars = ["py_result"]
        if to_py_code is not None:  # for non void return value
 
            if isinstance(to_py_code, basestring):
                to_py_code = "    %s" % to_py_code
            fun_code.add(to_py_code)
            fun_code.add("    return %s" % (", ".join(out_vars)))
 
        return fun_code
 
    def create_wrapper_for_constructor(self, class_decl, constructors):
        real_constructors = []
        codes = []
        for cons in constructors:
            if len(cons.arguments) == 1:
                (n, t), = cons.arguments
                if t.base_type == class_decl.name and t.is_ref:
                    code = self.create_special_copy_method(class_decl)
                    codes.append(code)
                    continue
            real_constructors.append(cons)
 
        if len(real_constructors) == 1:
            code = self.create_wrapper_for_nonoverloaded_constructor(class_decl,
                                                                     "__init__",
                                                                     real_constructors[0])
            codes.append(code)
        else:
            dispatched_cons_names = []
            for (i, constructor) in enumerate(real_constructors):
                dispatched_cons_name = "_init_%d" % i
                dispatched_cons_names.append(dispatched_cons_name)
                code = self.create_wrapper_for_nonoverloaded_constructor(class_decl,
                                                                         dispatched_cons_name,
                                                                         constructor)
                codes.append(code)
            code = self._create_overloaded_method_decl("__init__", dispatched_cons_names,
                                                       constructors, False)
            codes.append(code)
        return codes
 
    def create_wrapper_for_nonoverloaded_constructor(self, class_decl, py_name,
                                                     cons_decl):
 
        """ py_name ist name for constructor, as we dispatch overloaded
            constructors in __init__() the name of the method calling the
            c++ constructor is variable and given by `py_name`.
 
        """
        L.info("   create wrapper for non overloaded constructor %s" % py_name)
        cons_code = Code.Code()
 
        call_args, cleanups, in_types =\
            self._create_fun_decl_and_input_conversion(cons_code, py_name, cons_decl)
 
        # create instance of wrapped class
        call_args_str = ", ".join(call_args)
        name = class_decl.name
        cy_type = self.cr.cython_type(name)
        cons_code.add(
            """    self.inst = shared_ptr[$cy_type](new $cy_type($call_args_str))""", locals())
 
        for cleanup in reversed(cleanups):
            if not cleanup:
                continue
            if isinstance(cleanup, basestring):
                cleanup = "    %s" % cleanup
            cons_code.add(cleanup)
 
        return cons_code
 
    def create_special_add_method(self, cdcl, mdcl):
        L.info("   create wrapper for operator+")
        assert len(mdcl.arguments) == 1, "operator+ has wrong signature"
        (__, t), = mdcl.arguments
        name = cdcl.name
        assert t.base_type == name, "can only add to myself"
        assert mdcl.result_type.base_type == name, "can only return same type"
        cy_t = self.cr.cython_type(t)
        code = Code.Code()
        code.add("""
        |
        |def __add__($name self, $name other not None):
        |    cdef $cy_t  * this = self.inst.get()
        |    cdef $cy_t * that = other.inst.get()
        |    cdef $cy_t added = deref(this) + deref(that)
        |    cdef $name result = $name.__new__($name)
        |    result.inst = shared_ptr[$cy_t](new $cy_t(added))
        |    return result
        """, locals())
        return code
 
    def create_special_iadd_method(self, cdcl, mdcl):
        L.info("   create wrapper for operator+")
        assert len(mdcl.arguments) == 1, "operator+ has wrong signature"
        (__, t), = mdcl.arguments
        name = cdcl.name
        assert t.base_type == name, "can only add to myself"
        assert mdcl.result_type.base_type == name, "can only return same type"
        cy_t = self.cr.cython_type(t)
        code = Code.Code()
        code.add("""
        |
        |def __iadd__($name self, $name other not None):
        |    cdef $cy_t * this = self.inst.get()
        |    cdef $cy_t * that = other.inst.get()
        |    _iadd(this, that)
        |    return self
        """, locals())
 
        tl = Code.Code()
        tl.add("""
                |cdef extern from "autowrap_tools.hpp":
                |    void _iadd($cy_t *, $cy_t *)
                """, locals())
 
        self.top_level_code.append(tl)
 
        return code
 
    def create_special_getitem_method(self, mdcl):
        L.info("   create wrapper for operator[]")
        meth_code = Code.Code()
 
        (call_arg,), cleanups, in_types =\
            self._create_fun_decl_and_input_conversion(meth_code, "__getitem__", mdcl)
 
        meth_code.add("""
                     |    if $call_arg < 0:
                     |        raise IndexError("invalid index %d" % $call_arg)
                     """, locals())
 
        size_guard = mdcl.cpp_decl.annotations.get("wrap-upper-limit")
        if size_guard:
            meth_code.add("""
                     |    if $call_arg >= self.inst.get().$size_guard:
                     |        raise IndexError("invalid index %d" % $call_arg)
                     """, locals())
 
        # call wrapped method and convert result value back to python
 
        cy_call_str = "deref(self.inst.get())[%s]" % call_arg
 
        res_t = mdcl.result_type
        out_converter = self.cr.get(res_t)
        full_call_stmt = out_converter.call_method(res_t, cy_call_str)
 
        if isinstance(full_call_stmt, basestring):
            meth_code.add("""
                |    $full_call_stmt
                """, locals())
        else:
            meth_code.add(full_call_stmt)
 
        for cleanup in reversed(cleanups):
            if not cleanup:
                continue
            if isinstance(cleanup, basestring):
                cleanup = Code.Code().add(cleanup)
            meth_code.add(cleanup)
 
        out_var = "py_result"
        to_py_code = out_converter.output_conversion(res_t, "_r", out_var)
        if to_py_code is not None:  # for non void return value
 
            if isinstance(to_py_code, basestring):
                to_py_code = "    %s" % to_py_code
            meth_code.add(to_py_code)
            meth_code.add("    return $out_var", locals())
 
        return meth_code
 
    def create_cast_methods(self, mdecls):
        py_names = []
        for mdcl in mdecls:
            name = mdcl.cpp_decl.annotations.get("wrap-cast")
            if name is None:
                raise Exception("need wrap-cast annotation for %s" % mdcl)
            if name in py_names:
                raise Exception("wrap-cast annotation not unique for %s" % mdcl)
            py_names.append(name)
        codes = []
        for (py_name, mdecl) in zip(py_names, mdecls):
            code = Code.Code()
            res_t = mdecl.result_type
            cy_t = self.cr.cython_type(res_t)
            out_converter = self.cr.get(res_t)
 
            code.add("""
                     |
                     |def $py_name(self):""", locals())
 
            call_stmt = "<%s>(deref(self.inst.get()))" % cy_t
            full_call_stmt = out_converter.call_method(res_t, call_stmt)
 
            if isinstance(full_call_stmt, basestring):
                code.add("""
                    |    $full_call_stmt
                    """, locals())
            else:
                code.add(full_call_stmt)
 
            to_py_code = out_converter.output_conversion(res_t, "_r", "py_res")
            if isinstance(to_py_code, basestring):
                to_py_code = "    %s" % to_py_code
            code.add(to_py_code)
            code.add("""    return py_res""")
            codes.append(code)
        return codes
 
    def create_special_cmp_method(self, cdcl, ops):
        L.info("   create wrapper __richcmp__")
        meth_code = Code.Code()
        name = cdcl.name
        op_code_map = {'<': 0,
                       '==': 2,
                       '>': 4,
                       '<=': 1,
                       '!=': 3,
                       '>=': 5, }
        inv_op_code_map = dict((v, k) for (k, v) in op_code_map.items())
 
        implemented_op_codes = tuple(op_code_map[k] for (k, v) in ops.items() if v)
        meth_code.add("""
           |
           |def __richcmp__(self, other, op):
           |    if op not in $implemented_op_codes:
           |       op_str = $inv_op_code_map[op]
           |       raise Exception("comparions operator %s not implemented" % op_str)
           |    if not isinstance(other, $name):
           |        return False
           |    cdef $name other_casted = other
           |    cdef $name self_casted = self
           """, locals())
 
        for op in implemented_op_codes:
            op_sign = inv_op_code_map[op]
            meth_code.add("""    if op==$op:
                            |        return deref(self_casted.inst.get())
                            + $op_sign deref(other_casted.inst.get())""",
                          locals())
        return meth_code
 
    def create_special_copy_method(self, class_decl):
        L.info("   create wrapper __copy__")
        meth_code = Code.Code()
        name = class_decl.name
        cy_type = self.cr.cython_type(name)
        meth_code.add("""
                        |
                        |def __copy__(self):
                        |   cdef $name rv = $name.__new__($name)
                        |   rv.inst = shared_ptr[$cy_type](new $cy_type(deref(self.inst.get())))
                        |   return rv
                        """, locals())
        return meth_code
 
    def create_cimports(self):
        self.create_std_cimports()
        code = Code.Code()
        for resolved in self.resolved:
            import_from = resolved.pxd_import_path
            name = resolved.name
            if resolved.__class__ in (ResolvedEnum,):
                code.add("from $import_from cimport $name as _$name", locals())
            elif resolved.__class__ in (ResolvedClass, ):
                name = resolved.cpp_decl.name
                code.add("from $import_from cimport $name as _$name", locals())
            elif resolved.__class__ in (ResolvedFunction, ):
                mangled_name = "_" + name + "_" + import_from
                code.add("from $import_from cimport $name as $mangled_name", locals())
            elif resolved.__class__ in (ResolvedTypeDef, ):
                code.add("from $import_from cimport $name", locals())
 
        self.top_level_code.append(code)
 
    def create_std_cimports(self):
        code = Code.Code()
        code.add("""
                   |#cython: c_string_encoding=ascii  # for cython>=0.19
                   |from  libcpp.string  cimport string as libcpp_string
                   |from  libcpp.set     cimport set as libcpp_set
                   |from  libcpp.vector  cimport vector as libcpp_vector
                   |from  libcpp.pair    cimport pair as libcpp_pair
                   |from  libcpp.map     cimport map  as libcpp_map
                   |from  smart_ptr cimport shared_ptr
                   |from  AutowrapRefHolder cimport AutowrapRefHolder
                   |from  libcpp cimport bool
                   |from  libc.string cimport const_char
                   |from cython.operator cimport dereference as deref,
                   + preincrement as inc, address as address
 
                   """)
        if self.extra_cimports is not None:
            for stmt in self.extra_cimports:
                code.add(stmt)
 
        self.top_level_code.append(code)
 
    def create_includes(self):
        code = Code.Code()
        code.add("""
                |cdef extern from "autowrap_tools.hpp":
                |    char * _cast_const_away(char *)
                """)
 
        self.top_level_code.append(code)