# encoding: utf-8

from __future__ import print_function
import pdb

__license__ = """

Copyright (c) 2012-2014, Uwe Schmitt, all rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

Neither the name of the ETH Zurich nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

from contextlib import contextmanager
import os.path
import sys
import re
from collections import defaultdict

from autowrap.ConversionProvider import setup_converter_registry
from autowrap.DeclResolver import (ResolvedClass, ResolvedEnum, ResolvedTypeDef,
                                   ResolvedFunction)
from autowrap.Types import CppType  # , printable
import autowrap.Code as Code

import logging as L

try:
    unicode = unicode
except NameError:
    # 'unicode' is undefined, must be Python 3
    str = str
    unicode = str
    bytes = bytes
    basestring = (str, bytes)
else:
    # 'unicode' exists, must be Python 2
    str = str
    unicode = unicode
    bytes = str
    basestring = basestring


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:
            fp.write(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)
            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_type,) =\
            self._create_fun_decl_and_input_conversion(meth_code, "__getitem__", mdcl)

        meth_code.add("""
                     |    cdef long _idx = $call_arg
                     """, locals())

        if in_type.is_unsigned:
            meth_code.add("""
                        |    if _idx < 0:
                        |        raise IndexError("invalid index %d" % _idx)
                        """, locals())

        size_guard = mdcl.cpp_decl.annotations.get("wrap-upper-limit")
        if size_guard:
            meth_code.add("""
                     |    if _idx >= self.inst.get().$size_guard:
                     |        raise IndexError("invalid index %d" % _idx)
                     """, 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())
        meth_code.add("""
                        |
                        |def __deepcopy__(self, memo):
                        |   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)
