"""Code generation for native classes and related wrappers."""

from typing import Optional, List, Tuple, Dict, Callable, Mapping, Set

from mypy.backports import OrderedDict

from mypyc.common import PREFIX, NATIVE_PREFIX, REG_PREFIX, use_fastcall
from mypyc.codegen.emit import Emitter, HeaderDeclaration, ReturnHandler
from mypyc.codegen.emitfunc import native_function_header
from mypyc.codegen.emitwrapper import (
    generate_dunder_wrapper, generate_hash_wrapper, generate_richcompare_wrapper,
    generate_bool_wrapper, generate_get_wrapper, generate_len_wrapper,
    generate_set_del_item_wrapper, generate_contains_wrapper, generate_bin_op_wrapper
)
from mypyc.ir.rtypes import RType, RTuple, object_rprimitive
from mypyc.ir.func_ir import FuncIR, FuncDecl, FUNC_STATICMETHOD, FUNC_CLASSMETHOD
from mypyc.ir.class_ir import ClassIR, VTableEntries
from mypyc.sametype import is_same_type
from mypyc.namegen import NameGenerator


def native_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
    return f'{NATIVE_PREFIX}{fn.cname(emitter.names)}'


def wrapper_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
    return f'{PREFIX}{fn.cname(emitter.names)}'


# We maintain a table from dunder function names to struct slots they
# correspond to and functions that generate a wrapper (if necessary)
# and return the function name to stick in the slot.
# TODO: Add remaining dunder methods
SlotGenerator = Callable[[ClassIR, FuncIR, Emitter], str]
SlotTable = Mapping[str, Tuple[str, SlotGenerator]]

SLOT_DEFS: SlotTable = {
    '__init__': ('tp_init', lambda c, t, e: generate_init_for_class(c, t, e)),
    '__call__': ('tp_call', lambda c, t, e: generate_call_wrapper(c, t, e)),
    '__str__': ('tp_str', native_slot),
    '__repr__': ('tp_repr', native_slot),
    '__next__': ('tp_iternext', native_slot),
    '__iter__': ('tp_iter', native_slot),
    '__hash__': ('tp_hash', generate_hash_wrapper),
    '__get__': ('tp_descr_get', generate_get_wrapper),
}

AS_MAPPING_SLOT_DEFS: SlotTable = {
    '__getitem__': ('mp_subscript', generate_dunder_wrapper),
    '__setitem__': ('mp_ass_subscript', generate_set_del_item_wrapper),
    '__delitem__': ('mp_ass_subscript', generate_set_del_item_wrapper),
    '__len__': ('mp_length', generate_len_wrapper),
}

AS_SEQUENCE_SLOT_DEFS: SlotTable = {
    '__contains__': ('sq_contains', generate_contains_wrapper),
}

AS_NUMBER_SLOT_DEFS: SlotTable = {
    '__bool__': ('nb_bool', generate_bool_wrapper),
    '__neg__': ('nb_negative', generate_dunder_wrapper),
    '__invert__': ('nb_invert', generate_dunder_wrapper),
    '__int__': ('nb_int', generate_dunder_wrapper),
    '__float__': ('nb_float', generate_dunder_wrapper),
    '__add__': ('nb_add', generate_bin_op_wrapper),
    '__radd__': ('nb_add', generate_bin_op_wrapper),
    '__sub__': ('nb_subtract', generate_bin_op_wrapper),
    '__rsub__': ('nb_subtract', generate_bin_op_wrapper),
    '__mul__': ('nb_multiply', generate_bin_op_wrapper),
    '__rmul__': ('nb_multiply', generate_bin_op_wrapper),
    '__mod__': ('nb_remainder', generate_bin_op_wrapper),
    '__rmod__': ('nb_remainder', generate_bin_op_wrapper),
    '__truediv__': ('nb_true_divide', generate_bin_op_wrapper),
    '__rtruediv__': ('nb_true_divide', generate_bin_op_wrapper),
    '__floordiv__': ('nb_floor_divide', generate_bin_op_wrapper),
    '__rfloordiv__': ('nb_floor_divide', generate_bin_op_wrapper),
    '__lshift__': ('nb_lshift', generate_bin_op_wrapper),
    '__rlshift__': ('nb_lshift', generate_bin_op_wrapper),
    '__rshift__': ('nb_rshift', generate_bin_op_wrapper),
    '__rrshift__': ('nb_rshift', generate_bin_op_wrapper),
    '__and__': ('nb_and', generate_bin_op_wrapper),
    '__rand__': ('nb_and', generate_bin_op_wrapper),
    '__or__': ('nb_or', generate_bin_op_wrapper),
    '__ror__': ('nb_or', generate_bin_op_wrapper),
    '__xor__': ('nb_xor', generate_bin_op_wrapper),
    '__rxor__': ('nb_xor', generate_bin_op_wrapper),
    '__matmul__': ('nb_matrix_multiply', generate_bin_op_wrapper),
    '__rmatmul__': ('nb_matrix_multiply', generate_bin_op_wrapper),
    '__iadd__': ('nb_inplace_add', generate_dunder_wrapper),
    '__isub__': ('nb_inplace_subtract', generate_dunder_wrapper),
    '__imul__': ('nb_inplace_multiply', generate_dunder_wrapper),
    '__imod__': ('nb_inplace_remainder', generate_dunder_wrapper),
    '__itruediv__': ('nb_inplace_true_divide', generate_dunder_wrapper),
    '__ifloordiv__': ('nb_inplace_floor_divide', generate_dunder_wrapper),
    '__ilshift__': ('nb_inplace_lshift', generate_dunder_wrapper),
    '__irshift__': ('nb_inplace_rshift', generate_dunder_wrapper),
    '__iand__': ('nb_inplace_and', generate_dunder_wrapper),
    '__ior__': ('nb_inplace_or', generate_dunder_wrapper),
    '__ixor__': ('nb_inplace_xor', generate_dunder_wrapper),
    '__imatmul__': ('nb_inplace_matrix_multiply', generate_dunder_wrapper),
}

AS_ASYNC_SLOT_DEFS: SlotTable = {
    '__await__': ('am_await', native_slot),
    '__aiter__': ('am_aiter', native_slot),
    '__anext__': ('am_anext', native_slot),
}

SIDE_TABLES = [
    ('as_mapping', 'PyMappingMethods', AS_MAPPING_SLOT_DEFS),
    ('as_sequence', 'PySequenceMethods', AS_SEQUENCE_SLOT_DEFS),
    ('as_number', 'PyNumberMethods', AS_NUMBER_SLOT_DEFS),
    ('as_async', 'PyAsyncMethods', AS_ASYNC_SLOT_DEFS),
]

# Slots that need to always be filled in because they don't get
# inherited right.
ALWAYS_FILL = {
    '__hash__',
}


def generate_call_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
    if emitter.use_vectorcall():
        # Use vectorcall wrapper if supported (PEP 590).
        return 'PyVectorcall_Call'
    else:
        # On older Pythons use the legacy wrapper.
        return wrapper_slot(cl, fn, emitter)


def slot_key(attr: str) -> str:
    """Map dunder method name to sort key.

    Sort reverse operator methods and __delitem__ after others ('x' > '_').
    """
    if (attr.startswith('__r') and attr != '__rshift__') or attr == '__delitem__':
        return 'x' + attr
    return attr


def generate_slots(cl: ClassIR, table: SlotTable, emitter: Emitter) -> Dict[str, str]:
    fields: Dict[str, str] = OrderedDict()
    generated: Dict[str, str] = {}
    # Sort for determinism on Python 3.5
    for name, (slot, generator) in sorted(table.items(), key=lambda x: slot_key(x[0])):
        method_cls = cl.get_method_and_class(name)
        if method_cls and (method_cls[1] == cl or name in ALWAYS_FILL):
            if slot in generated:
                # Reuse previously generated wrapper.
                fields[slot] = generated[slot]
            else:
                # Generate new wrapper.
                name = generator(cl, method_cls[0], emitter)
                fields[slot] = name
                generated[slot] = name

    return fields


def generate_class_type_decl(cl: ClassIR, c_emitter: Emitter,
                             external_emitter: Emitter,
                             emitter: Emitter) -> None:
    context = c_emitter.context
    name = emitter.type_struct_name(cl)
    context.declarations[name] = HeaderDeclaration(
        f'PyTypeObject *{emitter.type_struct_name(cl)};',
        needs_export=True)

    # If this is a non-extension class, all we want is the type object decl.
    if not cl.is_ext_class:
        return

    generate_object_struct(cl, external_emitter)
    generate_full = not cl.is_trait and not cl.builtin_base
    if generate_full:
        context.declarations[emitter.native_function_name(cl.ctor)] = HeaderDeclaration(
            f'{native_function_header(cl.ctor, emitter)};',
            needs_export=True,
        )


def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
    """Generate C code for a class.

    This is the main entry point to the module.
    """
    name = cl.name
    name_prefix = cl.name_prefix(emitter.names)

    setup_name = f'{name_prefix}_setup'
    new_name = f'{name_prefix}_new'
    members_name = f'{name_prefix}_members'
    getseters_name = f'{name_prefix}_getseters'
    vtable_name = f'{name_prefix}_vtable'
    traverse_name = f'{name_prefix}_traverse'
    clear_name = f'{name_prefix}_clear'
    dealloc_name = f'{name_prefix}_dealloc'
    methods_name = f'{name_prefix}_methods'
    vtable_setup_name = f'{name_prefix}_trait_vtable_setup'

    fields: Dict[str, str] = OrderedDict()
    fields['tp_name'] = f'"{name}"'

    generate_full = not cl.is_trait and not cl.builtin_base
    needs_getseters = cl.needs_getseters or not cl.is_generated

    if not cl.builtin_base:
        fields['tp_new'] = new_name

    if generate_full:
        fields['tp_dealloc'] = f'(destructor){name_prefix}_dealloc'
        fields['tp_traverse'] = f'(traverseproc){name_prefix}_traverse'
        fields['tp_clear'] = f'(inquiry){name_prefix}_clear'
    if needs_getseters:
        fields['tp_getset'] = getseters_name
    fields['tp_methods'] = methods_name

    def emit_line() -> None:
        emitter.emit_line()

    emit_line()

    # If the class has a method to initialize default attribute
    # values, we need to call it during initialization.
    defaults_fn = cl.get_method('__mypyc_defaults_setup')

    # If there is a __init__ method, we'll use it in the native constructor.
    init_fn = cl.get_method('__init__')

    # Fill out slots in the type object from dunder methods.
    fields.update(generate_slots(cl, SLOT_DEFS, emitter))

    # Fill out dunder methods that live in tables hanging off the side.
    for table_name, type, slot_defs in SIDE_TABLES:
        slots = generate_slots(cl, slot_defs, emitter)
        if slots:
            table_struct_name = generate_side_table_for_class(cl, table_name, type, slots, emitter)
            fields[f'tp_{table_name}'] = f'&{table_struct_name}'

    richcompare_name = generate_richcompare_wrapper(cl, emitter)
    if richcompare_name:
        fields['tp_richcompare'] = richcompare_name

    # If the class inherits from python, make space for a __dict__
    struct_name = cl.struct_name(emitter.names)
    if cl.builtin_base:
        base_size = f'sizeof({cl.builtin_base})'
    elif cl.is_trait:
        base_size = 'sizeof(PyObject)'
    else:
        base_size = f'sizeof({struct_name})'
    # Since our types aren't allocated using type() we need to
    # populate these fields ourselves if we want them to have correct
    # values. PyType_Ready will inherit the offsets from tp_base but
    # that isn't what we want.

    # XXX: there is no reason for the __weakref__ stuff to be mixed up with __dict__
    if cl.has_dict:
        # __dict__ lives right after the struct and __weakref__ lives right after that
        # TODO: They should get members in the struct instead of doing this nonsense.
        weak_offset = f'{base_size} + sizeof(PyObject *)'
        emitter.emit_lines(
            f'PyMemberDef {members_name}[] = {{',
            f'{{"__dict__", T_OBJECT_EX, {base_size}, 0, NULL}},',
            f'{{"__weakref__", T_OBJECT_EX, {weak_offset}, 0, NULL}},',
            '{0}',
            '};',
        )

        fields['tp_members'] = members_name
        fields['tp_basicsize'] = f'{base_size} + 2*sizeof(PyObject *)'
        fields['tp_dictoffset'] = base_size
        fields['tp_weaklistoffset'] = weak_offset
    else:
        fields['tp_basicsize'] = base_size

    if generate_full:
        # Declare setup method that allocates and initializes an object. type is the
        # type of the class being initialized, which could be another class if there
        # is an interpreted subclass.
        emitter.emit_line(f'static PyObject *{setup_name}(PyTypeObject *type);')
        assert cl.ctor is not None
        emitter.emit_line(native_function_header(cl.ctor, emitter) + ';')

        emit_line()
        generate_new_for_class(cl, new_name, vtable_name, setup_name, emitter)
        emit_line()
        generate_traverse_for_class(cl, traverse_name, emitter)
        emit_line()
        generate_clear_for_class(cl, clear_name, emitter)
        emit_line()
        generate_dealloc_for_class(cl, dealloc_name, clear_name, emitter)
        emit_line()

        if cl.allow_interpreted_subclasses:
            shadow_vtable_name: Optional[str] = generate_vtables(
                cl, vtable_setup_name + "_shadow", vtable_name + "_shadow", emitter, shadow=True
            )
            emit_line()
        else:
            shadow_vtable_name = None
        vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter, shadow=False)
        emit_line()
    if needs_getseters:
        generate_getseter_declarations(cl, emitter)
        emit_line()
        generate_getseters_table(cl, getseters_name, emitter)
        emit_line()

    if cl.is_trait:
        generate_new_for_trait(cl, new_name, emitter)

    generate_methods_table(cl, methods_name, emitter)
    emit_line()

    flags = ['Py_TPFLAGS_DEFAULT', 'Py_TPFLAGS_HEAPTYPE', 'Py_TPFLAGS_BASETYPE']
    if generate_full:
        flags.append('Py_TPFLAGS_HAVE_GC')
    if cl.has_method('__call__') and emitter.use_vectorcall():
        fields['tp_vectorcall_offset'] = 'offsetof({}, vectorcall)'.format(
            cl.struct_name(emitter.names))
        flags.append('_Py_TPFLAGS_HAVE_VECTORCALL')
    fields['tp_flags'] = ' | '.join(flags)

    emitter.emit_line(f"static PyTypeObject {emitter.type_struct_name(cl)}_template_ = {{")
    emitter.emit_line("PyVarObject_HEAD_INIT(NULL, 0)")
    for field, value in fields.items():
        emitter.emit_line(f".{field} = {value},")
    emitter.emit_line("};")
    emitter.emit_line("static PyTypeObject *{t}_template = &{t}_template_;".format(
        t=emitter.type_struct_name(cl)))

    emitter.emit_line()
    if generate_full:
        generate_setup_for_class(
            cl, setup_name, defaults_fn, vtable_name, shadow_vtable_name, emitter)
        emitter.emit_line()
        generate_constructor_for_class(
            cl, cl.ctor, init_fn, setup_name, vtable_name, emitter)
        emitter.emit_line()
    if needs_getseters:
        generate_getseters(cl, emitter)


def getter_name(cl: ClassIR, attribute: str, names: NameGenerator) -> str:
    return names.private_name(cl.module_name, f'{cl.name}_get{attribute}')


def setter_name(cl: ClassIR, attribute: str, names: NameGenerator) -> str:
    return names.private_name(cl.module_name, f'{cl.name}_set{attribute}')


def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
    seen_attrs: Set[Tuple[str, RType]] = set()
    lines: List[str] = []
    lines += ['typedef struct {',
              'PyObject_HEAD',
              'CPyVTableItem *vtable;']
    if cl.has_method('__call__') and emitter.use_vectorcall():
        lines.append('vectorcallfunc vectorcall;')
    for base in reversed(cl.base_mro):
        if not base.is_trait:
            for attr, rtype in base.attributes.items():
                if (attr, rtype) not in seen_attrs:
                    lines.append('{}{};'.format(emitter.ctype_spaced(rtype),
                                                emitter.attr(attr)))
                    seen_attrs.add((attr, rtype))

                    if isinstance(rtype, RTuple):
                        emitter.declare_tuple_struct(rtype)

    lines.append(f'}} {cl.struct_name(emitter.names)};')
    lines.append('')
    emitter.context.declarations[cl.struct_name(emitter.names)] = HeaderDeclaration(
        lines,
        is_type=True
    )


def generate_vtables(base: ClassIR,
                     vtable_setup_name: str,
                     vtable_name: str,
                     emitter: Emitter,
                     shadow: bool) -> str:
    """Emit the vtables and vtable setup functions for a class.

    This includes both the primary vtable and any trait implementation vtables.
    The trait vtables go before the main vtable, and have the following layout:
        {
            CPyType_T1,         // pointer to type object
            C_T1_trait_vtable,  // pointer to array of method pointers
            C_T1_offset_table,  // pointer to array of attribute offsets
            CPyType_T2,
            C_T2_trait_vtable,
            C_T2_offset_table,
            ...
        }
    The method implementations are calculated at the end of IR pass, attribute
    offsets are {offsetof(native__C, _x1), offsetof(native__C, _y1), ...}.

    To account for both dynamic loading and dynamic class creation,
    vtables are populated dynamically at class creation time, so we
    emit empty array definitions to store the vtables and a function to
    populate them.

    If shadow is True, generate "shadow vtables" that point to the
    shadow glue methods (which should dispatch via the Python C-API).

    Returns the expression to use to refer to the vtable, which might be
    different than the name, if there are trait vtables.
    """

    def trait_vtable_name(trait: ClassIR) -> str:
        return '{}_{}_trait_vtable{}'.format(
            base.name_prefix(emitter.names), trait.name_prefix(emitter.names),
            '_shadow' if shadow else '')

    def trait_offset_table_name(trait: ClassIR) -> str:
        return '{}_{}_offset_table'.format(
            base.name_prefix(emitter.names), trait.name_prefix(emitter.names)
        )

    # Emit array definitions with enough space for all the entries
    emitter.emit_line('static CPyVTableItem {}[{}];'.format(
        vtable_name,
        max(1, len(base.vtable_entries) + 3 * len(base.trait_vtables))))

    for trait, vtable in base.trait_vtables.items():
        # Trait methods entry (vtable index -> method implementation).
        emitter.emit_line('static CPyVTableItem {}[{}];'.format(
            trait_vtable_name(trait),
            max(1, len(vtable))))
        # Trait attributes entry (attribute number in trait -> offset in actual struct).
        emitter.emit_line('static size_t {}[{}];'.format(
            trait_offset_table_name(trait),
            max(1, len(trait.attributes)))
        )

    # Emit vtable setup function
    emitter.emit_line('static bool')
    emitter.emit_line(f'{NATIVE_PREFIX}{vtable_setup_name}(void)')
    emitter.emit_line('{')

    if base.allow_interpreted_subclasses and not shadow:
        emitter.emit_line(f'{NATIVE_PREFIX}{vtable_setup_name}_shadow();')

    subtables = []
    for trait, vtable in base.trait_vtables.items():
        name = trait_vtable_name(trait)
        offset_name = trait_offset_table_name(trait)
        generate_vtable(vtable, name, emitter, [], shadow)
        generate_offset_table(offset_name, emitter, trait, base)
        subtables.append((trait, name, offset_name))

    generate_vtable(base.vtable_entries, vtable_name, emitter, subtables, shadow)

    emitter.emit_line('return 1;')
    emitter.emit_line('}')

    return vtable_name if not subtables else f"{vtable_name} + {len(subtables) * 3}"


def generate_offset_table(trait_offset_table_name: str,
                          emitter: Emitter,
                          trait: ClassIR,
                          cl: ClassIR) -> None:
    """Generate attribute offset row of a trait vtable."""
    emitter.emit_line(f'size_t {trait_offset_table_name}_scratch[] = {{')
    for attr in trait.attributes:
        emitter.emit_line('offsetof({}, {}),'.format(
            cl.struct_name(emitter.names), emitter.attr(attr)
        ))
    if not trait.attributes:
        # This is for msvc.
        emitter.emit_line('0')
    emitter.emit_line('};')
    emitter.emit_line('memcpy({name}, {name}_scratch, sizeof({name}));'.format(
        name=trait_offset_table_name)
    )


def generate_vtable(entries: VTableEntries,
                    vtable_name: str,
                    emitter: Emitter,
                    subtables: List[Tuple[ClassIR, str, str]],
                    shadow: bool) -> None:
    emitter.emit_line(f'CPyVTableItem {vtable_name}_scratch[] = {{')
    if subtables:
        emitter.emit_line('/* Array of trait vtables */')
        for trait, table, offset_table in subtables:
            emitter.emit_line(
                '(CPyVTableItem){}, (CPyVTableItem){}, (CPyVTableItem){},'.format(
                    emitter.type_struct_name(trait), table, offset_table))
        emitter.emit_line('/* Start of real vtable */')

    for entry in entries:
        method = entry.shadow_method if shadow and entry.shadow_method else entry.method
        emitter.emit_line('(CPyVTableItem){}{}{},'.format(
            emitter.get_group_prefix(entry.method.decl),
            NATIVE_PREFIX,
            method.cname(emitter.names)))

    # msvc doesn't allow empty arrays; maybe allowing them at all is an extension?
    if not entries:
        emitter.emit_line('NULL')
    emitter.emit_line('};')
    emitter.emit_line('memcpy({name}, {name}_scratch, sizeof({name}));'.format(name=vtable_name))


def generate_setup_for_class(cl: ClassIR,
                             func_name: str,
                             defaults_fn: Optional[FuncIR],
                             vtable_name: str,
                             shadow_vtable_name: Optional[str],
                             emitter: Emitter) -> None:
    """Generate a native function that allocates an instance of a class."""
    emitter.emit_line('static PyObject *')
    emitter.emit_line(f'{func_name}(PyTypeObject *type)')
    emitter.emit_line('{')
    emitter.emit_line(f'{cl.struct_name(emitter.names)} *self;')
    emitter.emit_line('self = ({struct} *)type->tp_alloc(type, 0);'.format(
        struct=cl.struct_name(emitter.names)))
    emitter.emit_line('if (self == NULL)')
    emitter.emit_line('    return NULL;')

    if shadow_vtable_name:
        emitter.emit_line(f'if (type != {emitter.type_struct_name(cl)}) {{')
        emitter.emit_line(f'self->vtable = {shadow_vtable_name};')
        emitter.emit_line('} else {')
        emitter.emit_line(f'self->vtable = {vtable_name};')
        emitter.emit_line('}')
    else:
        emitter.emit_line(f'self->vtable = {vtable_name};')

    if cl.has_method('__call__') and emitter.use_vectorcall():
        name = cl.method_decl('__call__').cname(emitter.names)
        emitter.emit_line(f'self->vectorcall = {PREFIX}{name};')

    for base in reversed(cl.base_mro):
        for attr, rtype in base.attributes.items():
            emitter.emit_line('self->{} = {};'.format(
                emitter.attr(attr), emitter.c_undefined_value(rtype)))

    # Initialize attributes to default values, if necessary
    if defaults_fn is not None:
        emitter.emit_lines(
            'if ({}{}((PyObject *)self) == 0) {{'.format(
                NATIVE_PREFIX, defaults_fn.cname(emitter.names)),
            'Py_DECREF(self);',
            'return NULL;',
            '}')

    emitter.emit_line('return (PyObject *)self;')
    emitter.emit_line('}')


def generate_constructor_for_class(cl: ClassIR,
                                   fn: FuncDecl,
                                   init_fn: Optional[FuncIR],
                                   setup_name: str,
                                   vtable_name: str,
                                   emitter: Emitter) -> None:
    """Generate a native function that allocates and initializes an instance of a class."""
    emitter.emit_line(f'{native_function_header(fn, emitter)}')
    emitter.emit_line('{')
    emitter.emit_line(f'PyObject *self = {setup_name}({emitter.type_struct_name(cl)});')
    emitter.emit_line('if (self == NULL)')
    emitter.emit_line('    return NULL;')
    args = ', '.join(['self'] + [REG_PREFIX + arg.name for arg in fn.sig.args])
    if init_fn is not None:
        emitter.emit_line('char res = {}{}{}({});'.format(
            emitter.get_group_prefix(init_fn.decl),
            NATIVE_PREFIX, init_fn.cname(emitter.names), args))
        emitter.emit_line('if (res == 2) {')
        emitter.emit_line('Py_DECREF(self);')
        emitter.emit_line('return NULL;')
        emitter.emit_line('}')

    # If there is a nontrivial ctor that we didn't define, invoke it via tp_init
    elif len(fn.sig.args) > 1:
        emitter.emit_line(
            'int res = {}->tp_init({});'.format(
                emitter.type_struct_name(cl),
                args))

        emitter.emit_line('if (res < 0) {')
        emitter.emit_line('Py_DECREF(self);')
        emitter.emit_line('return NULL;')
        emitter.emit_line('}')

    emitter.emit_line('return self;')
    emitter.emit_line('}')


def generate_init_for_class(cl: ClassIR,
                            init_fn: FuncIR,
                            emitter: Emitter) -> str:
    """Generate an init function suitable for use as tp_init.

    tp_init needs to be a function that returns an int, and our
    __init__ methods return a PyObject. Translate NULL to -1,
    everything else to 0.
    """
    func_name = f'{cl.name_prefix(emitter.names)}_init'

    emitter.emit_line('static int')
    emitter.emit_line(
        f'{func_name}(PyObject *self, PyObject *args, PyObject *kwds)')
    emitter.emit_line('{')
    emitter.emit_line('return {}{}(self, args, kwds) != NULL ? 0 : -1;'.format(
        PREFIX, init_fn.cname(emitter.names)))
    emitter.emit_line('}')

    return func_name


def generate_new_for_class(cl: ClassIR,
                           func_name: str,
                           vtable_name: str,
                           setup_name: str,
                           emitter: Emitter) -> None:
    emitter.emit_line('static PyObject *')
    emitter.emit_line(
        f'{func_name}(PyTypeObject *type, PyObject *args, PyObject *kwds)')
    emitter.emit_line('{')
    # TODO: Check and unbox arguments
    if not cl.allow_interpreted_subclasses:
        emitter.emit_line(f'if (type != {emitter.type_struct_name(cl)}) {{')
        emitter.emit_line(
            'PyErr_SetString(PyExc_TypeError, "interpreted classes cannot inherit from compiled");'
        )
        emitter.emit_line('return NULL;')
        emitter.emit_line('}')

    emitter.emit_line(f'return {setup_name}(type);')
    emitter.emit_line('}')


def generate_new_for_trait(cl: ClassIR,
                           func_name: str,
                           emitter: Emitter) -> None:
    emitter.emit_line('static PyObject *')
    emitter.emit_line(
        f'{func_name}(PyTypeObject *type, PyObject *args, PyObject *kwds)')
    emitter.emit_line('{')
    emitter.emit_line(f'if (type != {emitter.type_struct_name(cl)}) {{')
    emitter.emit_line(
        'PyErr_SetString(PyExc_TypeError, '
        '"interpreted classes cannot inherit from compiled traits");'
    )
    emitter.emit_line('} else {')
    emitter.emit_line(
        'PyErr_SetString(PyExc_TypeError, "traits may not be directly created");'
    )
    emitter.emit_line('}')
    emitter.emit_line('return NULL;')
    emitter.emit_line('}')


def generate_traverse_for_class(cl: ClassIR,
                                func_name: str,
                                emitter: Emitter) -> None:
    """Emit function that performs cycle GC traversal of an instance."""
    emitter.emit_line('static int')
    emitter.emit_line('{}({} *self, visitproc visit, void *arg)'.format(
        func_name,
        cl.struct_name(emitter.names)))
    emitter.emit_line('{')
    for base in reversed(cl.base_mro):
        for attr, rtype in base.attributes.items():
            emitter.emit_gc_visit(f'self->{emitter.attr(attr)}', rtype)
    if cl.has_dict:
        struct_name = cl.struct_name(emitter.names)
        # __dict__ lives right after the struct and __weakref__ lives right after that
        emitter.emit_gc_visit('*((PyObject **)((char *)self + sizeof({})))'.format(
            struct_name), object_rprimitive)
        emitter.emit_gc_visit(
            '*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))'.format(
                struct_name),
            object_rprimitive)
    emitter.emit_line('return 0;')
    emitter.emit_line('}')


def generate_clear_for_class(cl: ClassIR,
                             func_name: str,
                             emitter: Emitter) -> None:
    emitter.emit_line('static int')
    emitter.emit_line(f'{func_name}({cl.struct_name(emitter.names)} *self)')
    emitter.emit_line('{')
    for base in reversed(cl.base_mro):
        for attr, rtype in base.attributes.items():
            emitter.emit_gc_clear(f'self->{emitter.attr(attr)}', rtype)
    if cl.has_dict:
        struct_name = cl.struct_name(emitter.names)
        # __dict__ lives right after the struct and __weakref__ lives right after that
        emitter.emit_gc_clear('*((PyObject **)((char *)self + sizeof({})))'.format(
            struct_name), object_rprimitive)
        emitter.emit_gc_clear(
            '*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))'.format(
                struct_name),
            object_rprimitive)
    emitter.emit_line('return 0;')
    emitter.emit_line('}')


def generate_dealloc_for_class(cl: ClassIR,
                               dealloc_func_name: str,
                               clear_func_name: str,
                               emitter: Emitter) -> None:
    emitter.emit_line('static void')
    emitter.emit_line(f'{dealloc_func_name}({cl.struct_name(emitter.names)} *self)')
    emitter.emit_line('{')
    emitter.emit_line('PyObject_GC_UnTrack(self);')
    # The trashcan is needed to handle deep recursive deallocations
    emitter.emit_line(f'CPy_TRASHCAN_BEGIN(self, {dealloc_func_name})')
    emitter.emit_line(f'{clear_func_name}(self);')
    emitter.emit_line('Py_TYPE(self)->tp_free((PyObject *)self);')
    emitter.emit_line('CPy_TRASHCAN_END(self)')
    emitter.emit_line('}')


def generate_methods_table(cl: ClassIR,
                           name: str,
                           emitter: Emitter) -> None:
    emitter.emit_line(f'static PyMethodDef {name}[] = {{')
    for fn in cl.methods.values():
        if fn.decl.is_prop_setter or fn.decl.is_prop_getter:
            continue
        emitter.emit_line(f'{{"{fn.name}",')
        emitter.emit_line(f' (PyCFunction){PREFIX}{fn.cname(emitter.names)},')
        if use_fastcall(emitter.capi_version):
            flags = ['METH_FASTCALL']
        else:
            flags = ['METH_VARARGS']
        flags.append('METH_KEYWORDS')
        if fn.decl.kind == FUNC_STATICMETHOD:
            flags.append('METH_STATIC')
        elif fn.decl.kind == FUNC_CLASSMETHOD:
            flags.append('METH_CLASS')

        emitter.emit_line(' {}, NULL}},'.format(' | '.join(flags)))

    # Provide a default __getstate__ and __setstate__
    if not cl.has_method('__setstate__') and not cl.has_method('__getstate__'):
        emitter.emit_lines(
            '{"__setstate__", (PyCFunction)CPyPickle_SetState, METH_O, NULL},',
            '{"__getstate__", (PyCFunction)CPyPickle_GetState, METH_NOARGS, NULL},',
        )

    emitter.emit_line('{NULL}  /* Sentinel */')
    emitter.emit_line('};')


def generate_side_table_for_class(cl: ClassIR,
                                  name: str,
                                  type: str,
                                  slots: Dict[str, str],
                                  emitter: Emitter) -> Optional[str]:
    name = f'{cl.name_prefix(emitter.names)}_{name}'
    emitter.emit_line(f'static {type} {name} = {{')
    for field, value in slots.items():
        emitter.emit_line(f".{field} = {value},")
    emitter.emit_line("};")
    return name


def generate_getseter_declarations(cl: ClassIR, emitter: Emitter) -> None:
    if not cl.is_trait:
        for attr in cl.attributes:
            emitter.emit_line('static PyObject *')
            emitter.emit_line('{}({} *self, void *closure);'.format(
                getter_name(cl, attr, emitter.names),
                cl.struct_name(emitter.names)))
            emitter.emit_line('static int')
            emitter.emit_line('{}({} *self, PyObject *value, void *closure);'.format(
                setter_name(cl, attr, emitter.names),
                cl.struct_name(emitter.names)))

    for prop in cl.properties:
        # Generate getter declaration
        emitter.emit_line('static PyObject *')
        emitter.emit_line('{}({} *self, void *closure);'.format(
            getter_name(cl, prop, emitter.names),
            cl.struct_name(emitter.names)))

        # Generate property setter declaration if a setter exists
        if cl.properties[prop][1]:
            emitter.emit_line('static int')
            emitter.emit_line('{}({} *self, PyObject *value, void *closure);'.format(
                setter_name(cl, prop, emitter.names),
                cl.struct_name(emitter.names)))


def generate_getseters_table(cl: ClassIR,
                             name: str,
                             emitter: Emitter) -> None:
    emitter.emit_line(f'static PyGetSetDef {name}[] = {{')
    if not cl.is_trait:
        for attr in cl.attributes:
            emitter.emit_line(f'{{"{attr}",')
            emitter.emit_line(' (getter){}, (setter){},'.format(
                getter_name(cl, attr, emitter.names), setter_name(cl, attr, emitter.names)))
            emitter.emit_line(' NULL, NULL},')
    for prop in cl.properties:
        emitter.emit_line(f'{{"{prop}",')
        emitter.emit_line(f' (getter){getter_name(cl, prop, emitter.names)},')

        setter = cl.properties[prop][1]
        if setter:
            emitter.emit_line(f' (setter){setter_name(cl, prop, emitter.names)},')
            emitter.emit_line('NULL, NULL},')
        else:
            emitter.emit_line('NULL, NULL, NULL},')

    emitter.emit_line('{NULL}  /* Sentinel */')
    emitter.emit_line('};')


def generate_getseters(cl: ClassIR, emitter: Emitter) -> None:
    if not cl.is_trait:
        for i, (attr, rtype) in enumerate(cl.attributes.items()):
            generate_getter(cl, attr, rtype, emitter)
            emitter.emit_line('')
            generate_setter(cl, attr, rtype, emitter)
            if i < len(cl.attributes) - 1:
                emitter.emit_line('')
    for prop, (getter, setter) in cl.properties.items():
        rtype = getter.sig.ret_type
        emitter.emit_line('')
        generate_readonly_getter(cl, prop, rtype, getter, emitter)
        if setter:
            arg_type = setter.sig.args[1].type
            emitter.emit_line('')
            generate_property_setter(cl, prop, arg_type, setter, emitter)


def generate_getter(cl: ClassIR,
                    attr: str,
                    rtype: RType,
                    emitter: Emitter) -> None:
    attr_field = emitter.attr(attr)
    emitter.emit_line('static PyObject *')
    emitter.emit_line('{}({} *self, void *closure)'.format(getter_name(cl, attr, emitter.names),
                                                           cl.struct_name(emitter.names)))
    emitter.emit_line('{')
    attr_expr = f'self->{attr_field}'
    emitter.emit_undefined_attr_check(rtype, attr_expr, '==', unlikely=True)
    emitter.emit_line('PyErr_SetString(PyExc_AttributeError,')
    emitter.emit_line('    "attribute {} of {} undefined");'.format(repr(attr),
                                                                    repr(cl.name)))
    emitter.emit_line('return NULL;')
    emitter.emit_line('}')
    emitter.emit_inc_ref(f'self->{attr_field}', rtype)
    emitter.emit_box(f'self->{attr_field}', 'retval', rtype, declare_dest=True)
    emitter.emit_line('return retval;')
    emitter.emit_line('}')


def generate_setter(cl: ClassIR,
                    attr: str,
                    rtype: RType,
                    emitter: Emitter) -> None:
    attr_field = emitter.attr(attr)
    emitter.emit_line('static int')
    emitter.emit_line('{}({} *self, PyObject *value, void *closure)'.format(
        setter_name(cl, attr, emitter.names),
        cl.struct_name(emitter.names)))
    emitter.emit_line('{')

    deletable = cl.is_deletable(attr)
    if not deletable:
        emitter.emit_line('if (value == NULL) {')
        emitter.emit_line('PyErr_SetString(PyExc_AttributeError,')
        emitter.emit_line('    "{} object attribute {} cannot be deleted");'.format(repr(cl.name),
                                                                                    repr(attr)))
        emitter.emit_line('return -1;')
        emitter.emit_line('}')

    if rtype.is_refcounted:
        attr_expr = f'self->{attr_field}'
        emitter.emit_undefined_attr_check(rtype, attr_expr, '!=')
        emitter.emit_dec_ref(f'self->{attr_field}', rtype)
        emitter.emit_line('}')

    if deletable:
        emitter.emit_line('if (value != NULL) {')
    if rtype.is_unboxed:
        emitter.emit_unbox('value', 'tmp', rtype, error=ReturnHandler('-1'), declare_dest=True)
    elif is_same_type(rtype, object_rprimitive):
        emitter.emit_line('PyObject *tmp = value;')
    else:
        emitter.emit_cast('value', 'tmp', rtype, declare_dest=True)
        emitter.emit_lines('if (!tmp)',
                           '    return -1;')
    emitter.emit_inc_ref('tmp', rtype)
    emitter.emit_line(f'self->{attr_field} = tmp;')
    if deletable:
        emitter.emit_line('} else')
        emitter.emit_line('    self->{} = {};'.format(attr_field,
                                                      emitter.c_undefined_value(rtype)))
    emitter.emit_line('return 0;')
    emitter.emit_line('}')


def generate_readonly_getter(cl: ClassIR,
                             attr: str,
                             rtype: RType,
                             func_ir: FuncIR,
                             emitter: Emitter) -> None:
    emitter.emit_line('static PyObject *')
    emitter.emit_line('{}({} *self, void *closure)'.format(getter_name(cl, attr, emitter.names),
                                                           cl.struct_name(emitter.names)))
    emitter.emit_line('{')
    if rtype.is_unboxed:
        emitter.emit_line('{}retval = {}{}((PyObject *) self);'.format(
            emitter.ctype_spaced(rtype), NATIVE_PREFIX, func_ir.cname(emitter.names)))
        emitter.emit_box('retval', 'retbox', rtype, declare_dest=True)
        emitter.emit_line('return retbox;')
    else:
        emitter.emit_line('return {}{}((PyObject *) self);'.format(NATIVE_PREFIX,
                                                                   func_ir.cname(emitter.names)))
    emitter.emit_line('}')


def generate_property_setter(cl: ClassIR,
                             attr: str,
                             arg_type: RType,
                             func_ir: FuncIR,
                             emitter: Emitter) -> None:

    emitter.emit_line('static int')
    emitter.emit_line('{}({} *self, PyObject *value, void *closure)'.format(
        setter_name(cl, attr, emitter.names),
        cl.struct_name(emitter.names)))
    emitter.emit_line('{')
    if arg_type.is_unboxed:
        emitter.emit_unbox('value', 'tmp', arg_type, error=ReturnHandler('-1'),
                           declare_dest=True)
        emitter.emit_line('{}{}((PyObject *) self, tmp);'.format(
                          NATIVE_PREFIX,
                          func_ir.cname(emitter.names)))
    else:
        emitter.emit_line('{}{}((PyObject *) self, value);'.format(
                          NATIVE_PREFIX,
                          func_ir.cname(emitter.names)))
    emitter.emit_line('return 0;')
    emitter.emit_line('}')
