"""Generates a Cython wrappers from description dictionaries.
This module relies heavily on the type system to convert between C/C++, Cython, and
Python types in a seamless way.  While this module does not explicitly rely on the
auto-describer, it sure helps!  The functions in this module are conceptually
easy to understand -- given class descriptions they generate strings of Cython
code -- their implementations do a lot of heavy lifting.

This module is available as an xdress plugin by the name ``xdress.cythongen``.
Note that while the module does not rely on the autodescriber, the plugin does.

:author: <NAME> <<EMAIL>>

Cython Generation API
=====================
"""
from __future__ import print_function
import os
import sys
import math
import warnings
from numbers import Number

from .plugins import Plugin
from .types.matching import TypeMatcher, MatchAny
from .types.system import TypeSystem
from .utils import indent, expand_default_args, isclassdesc, isfuncdesc, \
    isvardesc, newoverwrite, sortedbytype, _lang_exts, Arg
from .version import cython_version, cython_version_info

if sys.version_info[0] >= 3:
    basestring = str

MATCH_REF = TypeMatcher((MatchAny, '&'))

AUTOGEN_WARNING = \
"""################################################
#                 WARNING!                     #
# This file has been auto-generated by xdress. #
# Do not modify!!!                             #
#                                              #
#                                              #
#                    Come on, guys. I mean it! #
################################################
"""

def gencpppxd(env, exceptions=True, ts=None):
    """Generates all cpp_*.pxd Cython header files for an environment of modules.

    Parameters
    ----------
    env : dict
        Environment dictonary mapping target module names to module description
        dictionaries.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cpppxds : dict
        Maps environment target names to Cython cpp_*.pxd header files strings.

    """
    ts = ts or TypeSystem()
    cpppxds = {}
    for name, mod in env.items():
        if mod['srcpxd_filename'] is None:
            continue
        cpppxds[name] = modcpppxd(mod, exceptions, ts=ts)
    return cpppxds

def _addotherclsnames(t, classes, name, others, ts):
    if t is None or t == (None, '*'):
        return
    spt = ts.strip_predicates(t)
    if spt in classes:
        others[name].add(spt)
    elif ts.isfunctionpointer(spt):
        for subt in spt[1:]:
            spsubt = ts.strip_predicates(subt)
            if spsubt in classes:
                others[name].add(spsubt)

def cpppxd_sorted_names(mod, ts):
    """Sorts the variable names in a cpp_*.pxd module so that C/C++
    declarations happen in the proper order.
    """
    classes = set([name for name, desc in mod.items() if isclassdesc(desc)])
    clssort = sorted(c for c in classes if isinstance(c, basestring))
    clssort += sorted(c for c in classes if not isinstance(c, basestring))
    othercls = {}
    for name in clssort:
        desc = mod[name]
        othercls[name] = set()
        for pitem in desc['parents']:
            _addotherclsnames(pitem, classes, name, othercls, ts)
        for aname, atype in desc['attrs'].items():
            _addotherclsnames(atype, classes, name, othercls, ts)
        for mkey, mval in desc['methods'].items():
            mname, margs = mkey[0], mkey[1:]
            _addotherclsnames(mval['return'], classes, name, othercls, ts)
            for marg in margs:
                _addotherclsnames(marg[1], classes, name, othercls, ts)
    clssort.sort(key=lambda x: len(othercls[x]))
    names = clssort[:1]
    for name in clssort[1:]:
        if name in names:
            continue
        for i, n in enumerate(names[:]):
            if name in othercls[n]:
                names.insert(i, name)
                break
            if othercls[name] <= set(names[:i+1] + [name]):
                names.insert(i+1, name)
                break
        else:
            names.append(name)
    names += sortedbytype([name for name, desc in mod.items() if isvardesc(desc)])
    names += sortedbytype([name for name, desc in mod.items() if isfuncdesc(desc)])
    return names


def modcpppxd(mod, exceptions=True, ts=None):
    """Generates a cpp_*.pxd Cython header file for exposing a C/C++ module to
    other Cython wrappers based off of a dictionary description of the module.

    Parameters
    ----------
    mod : dict
        Module description dictonary.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cpppxd : str
        Cython cpp_*.pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    m = {'extra': mod.get('extra', ''),
         "srcpxd_filename": mod.get("srcpxd_filename", "")}
    attrs = []
    cimport_tups = set()
    classnames = _classnames_in_mod(mod, ts)
    with ts.local_classes(classnames, frozenset(['c'])):
        for name in cpppxd_sorted_names(mod, ts):
            desc = mod[name]
            incfiles = desc['name']['incfiles']
            if 0 == len(incfiles):
                msg = "cythongen requires an include file for {0}, none found"
                raise ValueError(msg.format(name))
            elif 1 < len(incfiles):
                msg = "multiple include files found for {0}, choosing the first: {1}"
                warnings.warn(msg.format(name, incfiles[0]), RuntimeWarning)
            if isvardesc(desc):
                ci_tup, attr_str = varcpppxd(desc, exceptions, ts)
            elif isfuncdesc(desc):
                ci_tup, attr_str = funccpppxd(desc, exceptions, ts)
            elif isclassdesc(desc):
                ci_tup, attr_str = classcpppxd(desc, exceptions, ts)
            else:
                continue
            cimport_tups |= ci_tup
            attrs.append(attr_str)
    if mod.get('language', None) == 'c':
        cimport_tups.discard((ts.stlcontainers,))
    m['cimports'] = "\n".join(sorted(ts.cython_cimport_lines(cimport_tups)))
    m['attrs_block'] = "\n".join(attrs)
    t = '\n\n'.join([AUTOGEN_WARNING, '{cimports}', '{attrs_block}', '{extra}'])
    cpppxd = t.format(**m)
    return cpppxd

_cpppxd_var_template = \
"""# function signatures
cdef extern from "{header_filename}" {namespace}:

{variables_block}

{extra}
"""

def varcpppxd(desc, exceptions=True, ts=None):
    """Generates a cpp_*.pxd Cython header snippet for exposing a C/C++ variable
    to other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        Function description dictonary.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cimport_tups : set of tuples
        Set of Cython cimport tuples for cpp_*.pxd header file.
    cpppxd : str
        Cython cpp_*.pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    t = ts.canon(desc['type'])
    d = {'name': desc['name']['tarname'],
         'header_filename':  desc['name']['incfiles'][0],
         'namespace': _format_ns(desc),
         }

    inc = set(['c'])
    cimport_tups = set()
    ts.cython_cimport_tuples(t, cimport_tups, inc)

    vlines = []
    if ts.isenum(t):
        vlines.append("cdef enum {0}:".format(d['name']))
        enames = [name for name, val in t[1][2][2]]
        vlines += indent(enames, 4, join=False)
    else:
        ct = ts.cython_ctype(t)
        vlines.append("{0} {1}".format(ct, d['name']))
    d['variables_block'] = indent(vlines, 4)
    if 0 == len(d['variables_block'].strip()):
        return set(), ''

    d['extra'] = desc.get('extra', {}).get('cpppxd', '')
    cpppxd = _cpppxd_var_template.format(**d)
    extra = desc['extra']
    if 'srcpxd_filename' not in extra:
        ext = _lang_exts[name.language]
        extra['srcpxd_filename'] = '{0}_{1}.pxd'.format(ext, d['name']['tarbase'])
    return cimport_tups, cpppxd

_cpppxd_func_template = \
"""# function signatures
cdef extern from "{header_filename}" {namespace}:

{functions_block}

{extra}
"""

def funccpppxd(desc, exceptions=True, ts=None):
    """Generates a cpp_*.pxd Cython header snippet for exposing a C/C++ function
    to other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        Function description dictonary.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cimport_tups : set of tuples
        Set of Cython cimport tuples for cpp_*.pxd header file.
    cpppxd : str
        Cython cpp_*.pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    d = {'name': desc['name']['tarname'],
         'header_filename':  desc['name']['incfiles'][0],
         'namespace': _format_ns(desc),
         }
    inc = set(['c'])
    cimport_tups = set()

    flines = []
    funcitems = sorted(expand_default_args(desc['signatures'].items()))
    for fkey, frtn in funcitems:
        fname, fargs = fkey[0], fkey[1:]
        fbasename = fname if isinstance(fname, basestring) else fname[0]
        cppname = ts.cpp_funcname(fname)
        cyname = ts.cython_funcname(fname)
        if fbasename.startswith('_'):
            continue  # private
        if any([a[1] is None or a[1][0] is None for a in fargs + (frtn,)]):
            continue
        argfill = ", ".join([ts.cython_ctype(a[1]) for a in fargs])
        for a in fargs:
            ts.cython_cimport_tuples(a[1], cimport_tups, inc)
        estr = _exception_str(exceptions, desc['name']['language'], frtn, ts)
        if fname == cppname == cyname:
            line = "{0}({1}) {2}".format(fname, argfill, estr)
        else:
            line = '{0} "{1}" ({2}) {3}'.format(cyname, cppname, argfill, estr)
        rtype = ts.cython_ctype(frtn)
        ts.cython_cimport_tuples(frtn, cimport_tups, inc)
        line = rtype + " " + line
        if line not in flines:
            flines.append(line)
    d['functions_block'] = indent(flines, 4)
    if 0 == len(d['functions_block'].strip()):
        return set(), ''

    d['extra'] = desc.get('extra', {}).get('cpppxd', '')
    cpppxd = _cpppxd_func_template.format(**d)
    extra = desc['extra']
    if 'srcpxd_filename' not in extra:
        extra['srcpxd_filename'] = '{0}_{1}.pxd'.format(d['name']['tarbase'])
    return cimport_tups, cpppxd

_cpppxd_class_template = \
"""cdef extern from "{header_filename}" {namespace}:

    cdef {construct_kind} {name}{alias}{parents}:
        # constructors
{constructors_block}

        # attributes
{attrs_block}

        # methods
{methods_block}
        pass

{extra}
"""

def classcpppxd(desc, exceptions=True, ts=None):
    """Generates a cpp_*.pxd Cython header snippet for exposing a C/C++ class or
    struct to other Cython wrappers based off of a dictionary description of the
    class or struct.

    Parameters
    ----------
    desc : dict
        Class description dictonary.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cimport_tups : set of tuples
        Set of Cython cimport tuples for cpp_*.pxd header file.
    cpppxd : str
        Cython cpp_*.pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    src_lang = desc['name']['language']
    pars = ', '.join([ts.cython_ctype(p) for p in desc['parents']])
    d = {'parents': '('+pars+')' if pars else '',
         'header_filename': desc['name']['incfiles'][0],}
    d['namespace'] = _format_ns(desc)
    name = desc['name']['tarname']
    if isinstance(desc['type'], basestring):
        d['name'] = ts.cython_ctype(name)
        d['alias'] = ''
    else:
        d['name'] = ts.cython_classname(name)[1]
        d['alias'] = ' ' + _format_alias(desc, ts)

    construct_kinds = {'union': 'union'}
    if src_lang == 'c++':
        construct_kinds.update({'struct': 'cppclass', 'class': 'cppclass'})
        d['construct_kind'] = construct_kinds[desc.get('construct', 'class')]
    else:  # C
        construct_kinds.update({'struct': 'struct'})
        d['construct_kind'] = construct_kinds[desc.get('construct', 'struct')]
    inc = set(['c'])

    cimport_tups = set()
    for parent in desc['parents']:
        ts.cython_cimport_tuples(parent, cimport_tups, inc)

    alines = []
    attritems = sorted(desc['attrs'].items())
    for aname, atype in attritems:
        if aname.startswith('_'):
            continue
        actype = ts.cython_ctype(atype)
        if '{type_name}' in actype:
            aline = actype.format(type_name=aname)
        else:
            aline = "{0} {1}".format(actype, aname)
        alines.append(aline)
        ts.cython_cimport_tuples(atype, cimport_tups, inc)
    d['attrs_block'] = indent(alines, 8)

    mlines = []
    clines = []
    dargs = expand_default_args(desc['methods'].items())
    methitems = sorted(x for x in dargs if isinstance(x[0][0], basestring))
    methitems += sorted(x for x in dargs if not isinstance(x[0][0], basestring))
    default_constructor =  ((d['name'],),  None)
    if d['construct_kind'] == 'cppclass' and default_constructor not in methitems:
        methitems.insert(0, default_constructor)

    for mkey, mrtn in methitems:
        mname, margs = mkey[0], mkey[1:]
        mbasename = mname if isinstance(mname, basestring) else mname[0]
        mcppname = ts.cpp_funcname(mname)
        mcyname = ts.cython_funcname(mname)
        if mbasename.startswith('_') or mbasename.startswith('~'):
            continue  # private or destructor
        argfill = ", ".join([ts.cython_ctype(a[1]) for a in margs])
        for a in margs:
            ts.cython_cimport_tuples(a[1], cimport_tups, inc)
        estr = _exception_str(exceptions, src_lang, mrtn, ts)
        if mname == mcppname == mcyname:
            line = "{0}({1}) {2}".format(mname, argfill, estr)
        else:
            line = '{0} "{1}" ({2}) {3}'.format(mcyname, mcppname, argfill, estr)
        if mrtn is None:
            # this must be a constructor
            line = "{0}({1}) {2}".format(d['name'], argfill, estr)
            if line not in clines:
                clines.append(line)
        else:
            # this is a normal method
            if MATCH_REF.matches(mrtn):
                mrtn = mrtn[0]
            rtype = ts.cython_ctype(mrtn)
            ts.cython_cimport_tuples(mrtn, cimport_tups, inc)
            line = rtype + " " + line
            if line not in mlines:
                mlines.append(line)
    d['methods_block'] = indent(mlines, 8)
    d['constructors_block'] = indent(clines, 8) if src_lang == 'c++' else ''

    d['extra'] = desc.get('extra', {}).get('cpppxd', '')
    cpppxd = _cpppxd_class_template.format(**d)

    extra = desc['extra']
    if 'srcpxd_filename' not in desc:
        desc['srcpxd_filename'] = extra['srcpxd_filename']

    return cimport_tups, cpppxd


def genpxd(env, classes=(), ts=None, max_callbacks=8):
    """Generates all pxd Cython header files for an environment of modules.

    Parameters
    ----------
    env : dict
        Environment dictonary mapping target module names to module description
        dictionaries.
    classes : sequence, optional
        Listing of all class names that are handled by cythongen.  This may be
        the same dictionary as in genpyx()
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pxds : str
        Maps environment target names to Cython pxd header files strings.

    """
    ts = ts or TypeSystem()
    pxds = {}
    for name, mod in env.items():
        if mod['pxd_filename'] is None:
            continue
        pxds[name] = modpxd(mod, classes, ts=ts, max_callbacks=max_callbacks)
    return pxds

def pxd_sorted_names(mod):
    """Sorts the names in a module to make sure that pxd declarations happen in the
    proper order."""
    classnames = []
    othernames = []
    for name, desc in mod.items():
        if isclassdesc(desc):
            if name in classnames:
                continue
            parents = desc['parents']
            if not parents:
                classnames.insert(0, name)
                continue
            for parent in parents:
                if parent not in classnames and parent in mod:
                    classnames.append(parent)
            classnames.append(name)
        else:
            othernames.append(name)
    names = classnames + sortedbytype(othernames)
    return names


def modpxd(mod, classes=(), ts=None, max_callbacks=8):
    """Generates a pxd Cython header file for exposing C/C++ data to
    other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    mod : dict
        Module description dictonary.
    classes : sequence, optional
        Listing of all class names that are handled by cythongen.  This may be
        the same dictionary as in modpyx().
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pxd : str
        Cython .pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    m = {'extra': mod.get('extra', ''),
         "pxd_filename": mod.get("pxd_filename", "")}
    attrs = []
    cimport_tups = set()
    classnames = _classnames_in_mod(mod, ts)
    with ts.local_classes(classnames):
        for name in pxd_sorted_names(mod):
            desc = mod[name]
            if isclassdesc(desc):
                ci_tup, attr_str = classpxd(desc, classes, ts=ts,
                                            max_callbacks=max_callbacks)
            else:
                # no need to wrap functions again
                continue
            cimport_tups |= ci_tup
            attrs.append(attr_str)
    cimport_tups.discard((mod["name"],))
    if mod.get('language', None) == 'c':
        cimport_tups.discard((ts.stlcontainers,))
    m['cimports'] = "\n".join(sorted(ts.cython_cimport_lines(cimport_tups)))
    m['attrs_block'] = "\n".join(attrs)
    t = '\n\n'.join([AUTOGEN_WARNING, '{cimports}', '{attrs_block}', '{extra}'])
    pxd = t.format(**m)
    return pxd


_pxd_class_template = \
"""{function_pointer_block}

cdef class {name}{parents}:
{body}
    pass

{extra}
"""


def classpxd(desc, classes=(), ts=None, max_callbacks=8):
    """Generates a ``*pxd`` Cython header snippet for exposing a C/C++ class to
    other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        Class description dictonary.
    classes : sequence, optional
        Listing of all class names that are handled by cythongen.  This may be
        the same dictionary as in modpyx().
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    cimport_tups : set of tuples
        Set of Cython cimport tuples for .pxd header file.
    pxd : str
        Cython ``*.pxd`` header snippet for class.

    """
    ts = ts or TypeSystem()
    extra = desc['extra']
    if 'pxd_filename' not in extra:
        extra['pxd_filename'] = '{0}.pxd'.format(desc['name']['tarbase'])
    pars = ', '.join([ts.cython_cytype(p) for p in desc['parents']])
    d = {'parents': '('+pars+')' if pars else ''}
    name = desc['name']['tarname']
    d['name'] = ts.cython_classname(name)[1]
    max_callbacks = desc.get('extra', {}).get('max_callbacks', max_callbacks)
    mczeropad = int(math.log10(max_callbacks)) + 1

    cimport_tups = set()
    for parent in desc['parents']:
        ts.cython_cimport_tuples(parent, cimport_tups, set(['cy']))

    from_cpppxd = desc['srcpxd_filename'].rsplit('.', 1)[0]
    tarname = desc['name']['tarname']
    d['name_type'] = ts.cython_ctype(tarname)
    ts.cython_cimport_tuples(tarname, cimport_tups, set(['c']))

    body = [] if desc['parents'] else ['cdef void * _inst', 'cdef public bint _free_inst']
    attritems = sorted(desc['attrs'].items())
    fplines = []
    for aname, atype in attritems:
        if aname.startswith('_'):
            continue  # skip private
        _, _, cachename, iscached = ts.cython_c2py(aname, atype, cache_prefix=None)
        if iscached:
            ts.cython_cimport_tuples(atype, cimport_tups)
            if _isclassptr(atype, classes):
                atype_nopred = ts.strip_predicates(atype)
                cyt = ts.cython_cytype(atype_nopred)
            elif _isclassdblptr(atype, classes):
                cyt = 'list'
            else:
                cyt = ts.cython_cytype(atype)
            decl = "cdef public {0} {1}".format(cyt, cachename)
            body.append(decl)
        if ts.isfunctionpointer(atype):
            apyname, acname = _mangle_function_pointer_name(aname, name)
            acdecl = "cdef public " + ts.cython_ctype(('function',)+ atype[1:])
            for i in range(max_callbacks):
                suffix = "{0:0{1}}".format(i, mczeropad)
                apyname_i, acname_i = apyname + suffix, acname + suffix
                fplines.append("cdef public object " + apyname_i)
                fplines.append(acdecl.format(type_name=acname_i))
            body.append("cdef unsigned int _{0}_vtab_i".format(aname))
            fplines.append("cdef unsigned int _current_{0}_vtab_i".format(apyname))

    if len(fplines) > 0:
        fplines.append("cdef unsigned int _MAX_CALLBACKS_" + d['name'])

    d['body'] = indent(body or ['pass'])
    d['function_pointer_block'] = '\n'.join(fplines)
    d['extra'] = desc.get('extra', {}).get('pxd', '')
    pxd = _pxd_class_template.format(**d)
    return cimport_tups, pxd


def genpyx(env, classes=None, ts=None, max_callbacks=8):
    """Generates all pyx Cython implementation files for an environment of modules.

    Parameters
    ----------
    env : dict
        Environment dictonary mapping target module names to module description
        dictionaries.
    classes : dict, optional
        Dictionary which maps all class names that are required to
        their own descriptions.  This is required for resolving class heirarchy
        dependencies. If None, this will be computed here.
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pyxs : str
        Maps environment target names to Cython pxd header files strings.

    """
    ts = ts or TypeSystem()
    if classes is None:
        # get flat namespace of class descriptions
        classes = {}
        for envname, mod in env.items():
            for modname, desc in mod.items():
                if isclassdesc(desc):
                    classes[desc['name']] = desc
    # gen files
    pyxs = {}
    for name, mod in env.items():
        if mod['pyx_filename'] is None:
            continue
        pyxs[name] = modpyx(mod, classes=classes, ts=ts, max_callbacks=max_callbacks)
    return pyxs


_pyx_mod_template = AUTOGEN_WARNING + \
'''"""{docstring}
"""
{cimports}

{imports}

{attrs_block}

{extra}
'''

def modpyx(mod, classes=None, ts=None, max_callbacks=8):
    """Generates a pyx Cython implementation file for exposing C/C++ data to
    other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    mod : dict
        Module description dictonary.
    classes : dict, optional
        Dictionary which maps all class names that are required to
        their own descriptions.  This is required for resolving class heirarchy
        dependencies.
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pyx : str
        Cython pyx header file as in-memory string.

    """
    ts = ts or TypeSystem()
    m = {'extra': mod.get('extra', ''),
         'docstring': mod.get('docstring', "no docstring, please file a bug report!"),
         "pyx_filename": mod.get("pyx_filename", "")}
    attrs = []
    import_tups = set()
    cimport_tups = set()
    classnames = _classnames_in_mod(mod, ts)
    with ts.local_classes(classnames):
        for name, desc in mod.items():
            if isvardesc(desc):
                i_tup, ci_tup, attr_str = varpyx(desc, ts=ts)
            elif isfuncdesc(desc):
                i_tup, ci_tup, attr_str = funcpyx(desc, ts=ts)
            elif isclassdesc(desc):
                i_tup, ci_tup, attr_str = classpyx(desc, classes=classes, ts=ts,
                                                   max_callbacks=max_callbacks)
            else:
                continue
            import_tups |= i_tup
            cimport_tups |= ci_tup
            attrs.append(attr_str)
        # Add dispatcher for template functions
        template_funcs = _template_funcnames_in_mod(mod)
        template_dispatcher = _gen_template_func_dispatcher(template_funcs, ts)
        attrs.append(template_dispatcher)
        # Add dispatcher for template classes
        template_classes = _template_classnames_in_mod(mod)
        template_dispatcher = _gen_template_class_dispatcher(template_classes, ts)
        attrs.append(template_dispatcher)
    import_tups.discard((mod["name"],))
    #cimport_tups.discard((mod["name"],))  # remain commented for decls
    if mod.get('language', None) == 'c':
        import_tups.discard((ts.stlcontainers,))
        cimport_tups.discard((ts.stlcontainers,))
    m['imports'] = "\n".join(sorted(ts.cython_import_lines(import_tups)))
    m['cimports'] = "\n".join(sorted(ts.cython_cimport_lines(cimport_tups)))
    if 'numpy' in m['cimports']:
        m['imports'] += "\n\nnp.import_array()"
    m['attrs_block'] = "\n".join(attrs)
    t = '\n\n'.join([AUTOGEN_WARNING, '{cimports}', '{attrs_block}', '{extra}'])
    pyx = _pyx_mod_template.format(**m)
    return pyx

def _gen_template_pyfill(arg, kind, ts):
    """Generates the fill values for an argument of a type into a template type t.
    """
    if kind is Arg.TYPE:
        rtn = ts.cython_pytype(arg)
    elif kind is Arg.LIT:
        rnt = str(arg)
    elif kind is Arg.VAR:
        rtn = arg
    elif isinstance(arg, Number):
        rtn = str(arg)
    elif isinstance(arg, basestring):
        try:
            rtn = ts.cython_pytype(arg)
        except TypeError:
            rtn = arg
    return rtn

def _gen_template_func_dispatcher(templates, ts):
    """Generates a dictionary-based dispacher for template functions.
    """
    if 0 == len(templates):
        return ""
    templates = sorted(templates)
    disp = ['', "#", "# Function Dispatchers", "#",]
    alreadyinitd = set()
    for t in templates:
        initline = "{0} = {{}}".format(t[0])
        if initline not in alreadyinitd:
            disp.append("")
            disp.append("# {0} dispatcher".format(t[0]))
            disp.append(initline)
            alreadyinitd.add(initline)
        args = t[1:]
        pytype = ts.cython_funcname(t)
        kinds = ts.argument_kinds.get(t, ((Arg.NONE,))*(len(t)-1))
        if 0 == len(args):
            raise ValueError("type {0!r} not a template".format(t))
        elif 1 == len(args):
            disp.append("{0}[{1!r}] = {2}".format(t[0], t[1], pytype))
            disp.append("{0}[{1}] = {2}".format(t[0],
                _gen_template_pyfill(t[1], kinds[0], ts), pytype))
        else:
            rs = [repr(_) for _ in t[1:]]
            pyts = [_gen_template_pyfill(x, k, ts) for x, k in zip(t[1:], kinds)]
            disp.append("{0}[{1}] = {2}".format(t[0], ", ".join(rs), pytype))
            disp.append("{0}[{1}] = {2}".format(t[0], ", ".join(pyts), pytype))
    return "\n".join(disp)

def _gen_template_class_dispatcher(templates, ts):
    """Generates a dictionary-based dispacher for template classes.
    """
    if 0 == len(templates):
        return ""
    templates = sorted(templates)
    disp = ['', "#", "# Class Dispatchers", "#",]
    alreadyinitd = set()
    for t in templates:
        initline = "{0} = {{}}".format(t[0])
        if initline not in alreadyinitd:
            disp.append("")
            disp.append("# {0} Dispatcher".format(t[0]))
            disp.append(initline)
            alreadyinitd.add(initline)
        args = t[1:-1]
        pytype = ts.cython_pytype(t)
        kinds = ts.argument_kinds.get(t, ((Arg.NONE,))*(len(t)-1))
        if 0 == len(args):
            raise ValueError("type {0!r} not a template".format(t))
        elif 1 == len(args):
            disp.append("{0}[{1!r}] = {2}".format(t[0], t[1], pytype))
            disp.append("{0}[{1}] = {2}".format(t[0],
                _gen_template_pyfill(t[1], kinds[0], ts), pytype))
        else:
            rs = [repr(_) for _ in t[1:-1]]
            pyts = [_gen_template_pyfill(x, k, ts) for x, k in zip(t[1:], kinds)]
            disp.append("{0}[{1}] = {2}".format(t[0], ", ".join(rs), pytype))
            disp.append("{0}[{1}] = {2}".format(t[0], ", ".join(pyts), pytype))
    return "\n".join(disp)

def _gen_property_get(name, t, ts, cached_names=None, inst_name="self._inst",
                      classes=()):
    """This generates a Cython property getter for a variable of a given
    name and type."""
    lines = ['def __get__(self):']
    decl, body, rtn, iscached = ts.cython_c2py(name, t, inst_name=inst_name)
    if decl is not None:
        if _isclassptr(t, classes):
            decl, _, _, _ = ts.cython_c2py(name, t[0], inst_name=inst_name)
        lines += indent(decl, join=False)
    if body is not None:
        lines += indent(body, join=False)
    if iscached and cached_names is not None:
        cached_names.append(rtn)
    lines += indent("return {0}".format(rtn), join=False)
    return lines

def _gen_property_set(name, t, ts, inst_name="self._inst", cached_name=None,
                      classes=()):
    """This generates a Cython property setter for a variable of a given
    name and type."""
    lines = ['def __set__(self, value):']
    decl, body, rtn = ts.cython_py2c('value', t)
    if decl is not None:
        lines += indent(decl, join=False)
    if body is not None:
        lines += indent(body, join=False)
    lines += indent("{0}.{1} = {2}".format(inst_name, name, rtn), join=False)
    if cached_name is not None:
        lines += indent("{0} = None".format(cached_name), join=False)
    return lines

def _gen_property(name, t, ts, doc=None, cached_names=None, inst_name="self._inst",
                  classes=()):
    """This generates a Cython property for a variable of a given name and type."""
    lines  = ['property {0}:'.format(name)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    oldcnlen = 0 if cached_names is None else len(cached_names)
    lines += indent(_gen_property_get(name, t, ts, cached_names=cached_names,
                    inst_name=inst_name, classes=classes), join=False)
    lines += ['']
    newcnlen = 0 if cached_names is None else len(cached_names)
    cached_name = cached_names[-1] if newcnlen == 1 + oldcnlen else None
    lines += indent(_gen_property_set(name, t, ts, inst_name=inst_name,
                    cached_name=cached_name, classes=classes), join=False)
    lines += ['', ""]
    return lines

def _gen_function_pointer_property(name, t, ts, doc=None, cached_names=None,
        inst_name="self._inst", classname='', max_callbacks=8):
    """This generates a Cython property for a function pointer variable."""
    lines  = ['property {0}:'.format(name)]

    # get section
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    oldcnlen = 0 if cached_names is None else len(cached_names)
    lines += indent(_gen_property_get(name, t, ts, cached_names=cached_names,
                                      inst_name=inst_name), join=False)

    # set section
    mczeropad = int(math.log10(max_callbacks)) + 1
    lines += [""]
    newcnlen = 0 if cached_names is None else len(cached_names)
    cached_name = cached_names[-1] if newcnlen == 1 + oldcnlen else None
    setlines = indent(_gen_property_set(name, ('void', '*'), ts, inst_name=inst_name,
                                        cached_name=cached_name), join=False)

    lines += setlines[:1]
    lines += indent(indent(['if not callable(value):',
                ('    raise ValueError("{0!r} is not callable but ' + classname +
                 '.' + name + ' is a function pointer!".format(value))')],
                join=False), join=False)
    #lines += setlines[1:]
    pyname, cname = _mangle_function_pointer_name(name, classname)
    pynames = [pyname + "{0:0{1}}".format(i, mczeropad) for i in \
                                                    range(max_callbacks)]
    cnames = [cname + "{0:0{1}}".format(i, mczeropad) for i in \
                                                    range(max_callbacks)]
    if max_callbacks == 1:
        suffix = '0'
        extraset = ('global {pyname}\n'
                    '{cached_name} = value\n'
                    '{pyname} = value\n'
                    '{inst_name}.{name} = {cname}\n'
                    ).format(name=name, pyname=pyname + suffix, cname=cname + suffix,
                             cached_name=cached_name, inst_name=inst_name)
    elif max_callbacks > 1:
        extraset = ['cdef unsigned int vtab_i',
                    '{cached_name} = value'.format(cached_name=cached_name),
                    "global " + ', '.join(pynames) + \
                                ', _current_{0}_vtab_i'.format(pyname),]
        selectlines = []
        for i, pyname_i in enumerate(pynames):
            selectlines.append("elif {0} is None:".format(pyname_i))
            selectlines.append("    vtab_i = {0}".format(i))
        selectlines[0] = selectlines[0][2:]
        extraset += selectlines
        extraset += ['else:',
                     ('    warnings.warn("Ran out of available callbacks for '
                      '{0}.{1}, overriding existing callback.", RuntimeWarning)'
                      ).format(classname, name),
                     '    vtab_i = _current_{0}_vtab_i'.format(pyname),
                     '    _current_{0}_vtab_i = (_current_{0}_vtab_i+1)%{1}'.format(
                                                            pyname, max_callbacks),
                     'self._{0}_vtab_i = vtab_i'.format(name),]
        setvallines = []
        for i, (pyname_i, cname_i) in enumerate(zip(pynames, cnames)):
            setvallines.append("elif vtab_i == {0}:".format(i))
            setvallines.append("    {pyname} = value".format(pyname=pyname_i))
            setvallines.append("    {inst_name}.{name} = {cname}".format(
                                inst_name=inst_name, name=name, cname=cname_i))
        setvallines[0] = setvallines[0][2:]
        extraset += setvallines
    else:
        msg = "The max number of callbacks for {0} must be >=1, got {1}."
        raise RuntimeError(msg.format(classname, max_callbacks))
    lines += indent(indent(extraset, join=False), join=False)
    lines.append('')
    lines += ["def _deref_{0}_callback(self):".format(name),
              '    "Warning: this can have dangerous side effects!"',
              '    cdef unsigned int vtab_i',
              '    {cached_name} = None'.format(cached_name=cached_name),
              "    if self._{0}_vtab_i < {1}:".format(name, max_callbacks+1),
              '        vtab_i = self._{0}_vtab_i'.format(name),
              "        self._{0}_vtab_i = {1}".format(name, max_callbacks+1), ]
    dereflines = []
    for i, pyname_i in enumerate(pynames):
        dereflines.append("elif vtab_i == {0}:".format(i))
        dereflines.append("    global {0}".format(pyname_i))
        dereflines.append("    {0} = None".format(pyname_i))
    dereflines[0] = dereflines[0][2:]
    lines += indent(indent(dereflines, join=False), join=False)
    lines += ['', ""]
    return lines

def _gen_function_pointer_wrapper(name, t, ts, classname='', max_callbacks=8):
    """This generates a Cython wrapper for a function pointer variable."""
    pyname, cname = _mangle_function_pointer_name(name, classname)
    mczeropad = int(math.log10(max_callbacks)) + 1
    lines = ["#\n# Function pointer helpers for {1}.{0}\n#".format(name, classname),
             "_current_{0}_vtab_i = 0".format(pyname), ""]
    for i in range(max_callbacks):
        suffix = "{0:0{1}}".format(i, mczeropad)
        pyname_i, cname_i = pyname + suffix, cname + suffix
        decl, body, rtn = ts.cython_py2c(pyname_i, t, proxy_name=cname_i)
        lines += [pyname_i + " = None", '']
        lines += rtn.splitlines()
        lines.append('')
    lines += ['', ""]
    return lines

def _gen_argfill(args, defaults):
    """Generate argument list for a function, and return (argfill, names).
    If any argument names or empty, the corresponding entry in names will
    be '_n' for some integer n.
    """
    counter = 0
    taken = frozenset(a[0] for a in args)
    names = []
    afill = []
    for (name, t), (kind, default) in zip(args, defaults):
        if not name: # Empty name, generate a fresh dummy symbol
            while 1:
                name = '_%d'%counter
                counter += 1
                if name not in taken:
                    break
        names.append(name)
        if kind is Arg.NONE:
            afillval = name
        elif kind is Arg.LIT:
            afillval = "{0}={1!r}".format(name, default)
        elif kind is Arg.VAR:
            afillval = "{0}={1}".format(name, default)
        elif kind is Arg.TYPE:
            raise ValueError("default argument value cannot be a type: "
                             "{0}".format(name))
        else:
            raise ValueError("default argument value cannot be determined: "
                             "{0}".format(name))
        afill.append(afillval)
    return ", ".join(afill), names

def _gen_function(name, name_mangled, args, rtn, defaults, ts, doc=None,
                  inst_name="self._inst", is_method=False):
    argfill, names = _gen_argfill(args, defaults)
    if is_method:
        argfill = "self, " + argfill
    lines  = ['def {0}({1}):'.format(name_mangled, argfill)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    decls = []
    argbodies = []
    argrtns = {}
    for n,a in zip(names, args):
        adecl, abody, artn = ts.cython_py2c(n, a[1])
        if adecl is not None:
            decls += indent(adecl, join=False)
        if abody is not None:
            argbodies += indent(abody, join=False)
        argrtns[n] = artn
    rtype_orig = ts.cython_ctype(rtn)
    rtype = rtype_orig.replace('const ', "").replace(' &', '')
    hasrtn = rtype not in set(['None', None, 'NULL', 'void'])
    argvals = ', '.join(argrtns[n] for n in names)
    fcall = '{0}.{1}({2})'.format(inst_name, name, argvals)
    if hasrtn:
        fcdecl, fcbody, fcrtn, fccached = ts.cython_c2py('rtnval', rtn, cached=False, view=False)
        decls += indent("cdef {0} {1}".format(rtype, 'rtnval'), join=False)
        if 'const ' in rtype_orig:
            func_call = indent('rtnval = <{0}> {1}'.format(rtype, fcall), join=False)
        else:
            func_call = indent('rtnval = {0}'.format(fcall), join=False)
        if fcdecl is not None:
            decls += indent(fcdecl, join=False)
        if fcbody is not None:
            func_call += indent(fcbody, join=False)
        func_rtn = indent("return {0}".format(fcrtn), join=False)
    else:
        func_call = indent(fcall, join=False)
        func_rtn = []
    lines += decls
    lines += argbodies
    lines += func_call
    lines += func_rtn
    lines += ['', ""]
    return lines

def _gen_default_constructor(desc, attrs, ts, doc=None, srcpxd_filename=None):
    src_lang = desc['name']['language']
    args = ['self'] + [a + "=None" for a, _ in attrs] + ['*args', '**kwargs']
    argfill = ", ".join(args)
    lines  = ['def __init__({0}):'.format(argfill)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    ct = ts.cython_ctype(desc['type'])
    if desc['construct'] == 'class':
        fcall = 'self._inst = new {0}()'.format(ct)
    elif desc['construct'] in ('struct', 'union'):
        construct_template = 'self._inst = malloc(sizeof({0}))\n'
        if src_lang == 'c++' and desc['construct'] == 'struct':
            # Only call the default constructor when it makes sense.
            construct_template += '(<{0} *> self._inst)[0] = {0}()'
        fcall = construct_template.format(ct)
    else:
        raise ValueError('construct must be either "class", "struct" or "union".')
    lines.extend(indent(fcall, join=False))
    for a, _ in attrs:
        lines.append(indent("if {0} is not None:".format(a)))
        lines.append(indent("self.{0} = {0}".format(a), 8))
    lines += ['', ""]
    return lines

def _gen_constructor(name, name_mangled, classname, args, defaults, ts,
                     doc=None, srcpxd_filename=None, inst_name="self._inst",
                     construct="class", src_lang='c++'):
    argfill, names = _gen_argfill(args, defaults)
    lines  = ['def {0}(self, {1}):'.format(name_mangled, argfill)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    decls = []
    argbodies = []
    argrtns = {}
    for n,a in zip(names, args):
        adecl, abody, artn = ts.cython_py2c(n, a[1])
        if adecl is not None:
            decls += indent(adecl, join=False)
        if abody is not None:
            argbodies += indent(abody, join=False)
        argrtns[n] = artn
    argvals = ', '.join(argrtns[n] for n in names)
    classname = classname if srcpxd_filename is None else \
                    "{0}.{1}".format(srcpxd_filename.rsplit('.', 1)[0], classname)
    if construct == 'class':
        fcall = 'self._inst = new {0}({1})'.format(classname, argvals)
    elif construct in ('struct', 'union'):
        construct_template = 'self._inst = malloc(sizeof({0}))\n'
        if src_lang == 'c++' and construct == 'struct':
            # Only call the default constructor when it makes sense.
            construct_template += '(<{0} *> self._inst)[0] = {0}({1})'
        fcall = construct_template.format(classname, argvals)
    else:
        raise ValueError('construct must be either "class", "struct", or "union".')
    func_call = indent(fcall, join=False)
    lines += decls
    lines += argbodies
    lines += func_call
    lines += ['', ""]
    return lines

def _gen_dispatcher(name, name_mangled, ts, doc=None, hasrtn=True, is_method=True):
    argfill = ", ".join(['self', '*args', '**kwargs'])
    if is_method is True:
        # string to format for arg checking
        arg_chk_str = "if types <= self.{0}_argtypes:"

        # string to format for dispatching and returning
        dispatch_str_ret = "return self.{0}(*args, **kwargs)"
        dispatch_str_no_ret = "self.{0}(*args, **kwargs)"

        # Make self a method argument or not
        argfill = ", ".join(['self', '*args', '**kwargs'])
    else:
        arg_chk_str = "if types <= {0}_argtypes:"
        dispatch_str_ret = "return {0}(*args, **kwargs)"
        dispatch_str_no_ret = "{0}(*args, **kwargs)"
        argfill = ", ".join(['*args', '**kwargs'])
    lines  = ['def {0}({1}):'.format(name, argfill)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    types = ["types = set([(i, type(a)) for i, a in enumerate(args)])",
             "types.update([(k, type(v)) for k, v in kwargs.items()])",]
    lines += indent(types, join=False)
    refinenum = lambda x: (sum([int(ts.isrefinement(a[1])) for a in x[0][1:]]),
                           len(x[0]), x[1])
    mangitems = sorted(name_mangled.items(), key=refinenum)
    mtypeslines = []
    lines += indent("# vtable-like dispatch for exactly matching types", join=False)
    for key, mangled_name in mangitems:
        cargs = key[1:]
        arang = range(len(cargs))
        anames = [ca[0] for ca in cargs]
        pytypes = [ts.cython_pytype(ca[1]) for ca in cargs]
        mtypes = ", ".join(
            ["({0}, {1})".format(i, pyt) for i, pyt in zip(arang, pytypes)] + \
            ['("{0}", {1})'.format(n, pyt) for n, pyt in zip(anames, pytypes)])
        mtups = '(' + mtypes + ')' if 0 < len(mtypes) else mtypes
        mtypeslines.append(mangled_name + "_argtypes = frozenset(" + mtups + ")")
        cond = [arg_chk_str.format(mangled_name),]
        if hasrtn:
            rline = dispatch_str_ret.format(mangled_name)
        else:
            rline = [dispatch_str_no_ret.format(mangled_name), "return"]
        cond += indent(rline, join=False)
        lines += indent(cond, join=False)
    lines = sorted(mtypeslines) + [''] +  lines
    lines += indent("# duck-typed dispatch based on whatever works!", join=False)
    refineopp = lambda x: (-1*sum([int(ts.isrefinement(a[1])) for a in x[0][1:]]), len(x[0]), x[1])
    mangitems = sorted(name_mangled.items(), key=refineopp)
    for key, mangled_name in mangitems:
        lines += indent('try:', join=False)
        if hasrtn:
            rline = dispatch_str_ret.format(mangled_name)
        else:
            rline = [dispatch_str_no_ret.format(mangled_name), "return"]
        lines += indent(indent(rline, join=False), join=False)
        lines += indent(["except (RuntimeError, TypeError, NameError):",
                         indent("pass", join=False)[0],], join=False)
    errmsg = "raise RuntimeError('method {0}() could not be dispatched')".format(name)
    lines += indent(errmsg, join=False)
    lines += ['']
    return lines


def _class_heirarchy(cls, ch, classes):
    if not classes[cls]['parents']:
        return
    if 0 == len(ch) or ch[0] != cls:
        ch.insert(0, cls)
    for p in classes[cls]['parents'][::-1]:
        ch.insert(0, p)
        _class_heirarchy(p, ch, classes)

def _method_instance_names(desc, classes, key, rtn, ts):
    classnames = []
    _class_heirarchy(desc['name']['tarname'], classnames, classes)
    for classname in classnames:
        classrtn = classes.get(classname, {}).get('methods', {})\
                          .get(key, {}).get('return', NotImplemented)
        if rtn != classrtn:
            continue
        class_ctype = ts.cython_ctype(classname)
        inst_name = "(<{0} *> self._inst)".format(class_ctype)
        return inst_name, classname
    tarname = desc['name']['tarname']
    return "(<{0} *> self._inst)".format(ts.cython_ctype(tarname)), tarname


def _count0(x):
    c = {}
    for v in x:
        v0 = v[0]
        c[v0] = c.get(v0, 0) + 1
    return c

def _doc_add_sig(doc, name, args, defaults, ismethod=True):
    if doc.startswith(name):
        return doc
    sig = ['self'] if ismethod else []
    sig.append(_gen_argfill(args, defaults)[0])
    newdoc = "{0}({1})\n{2}".format(name, ", ".join(sig), doc)
    return newdoc


_pyx_class_template = \
'''{function_pointer_block}

cdef class {name}{parents}:
{class_docstring}

{cdefattrs}

    # constuctors
    def __cinit__(self, *args, **kwargs):
        self._inst = NULL
        self._free_inst = True

        # cached property defaults
{property_defaults}

{constructor_block}

    # attributes
{attrs_block}
    # methods
{methods_block}

    pass

{extra}
'''

def classpyx(desc, classes=None, ts=None, max_callbacks=8):
    """Generates a ``*.pyx`` Cython wrapper implementation for exposing a C/C++
    class based off of a dictionary description.  The environment is a
    dictionary of all class names known to their descriptions.

    Parameters
    ----------
    desc : dict
        Class description dictonary.
    classes : dict, optional
        Dictionary which maps all class names that are required to
        their own descriptions.  This is required for resolving class heirarchy
        dependencies.
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pyx : str
        Cython ``*.pyx`` implementation file as in-memory string.

    """
    ts = ts or TypeSystem()
    if classes is None:
        classes = {desc['name']['tarname']: desc}
    src_lang = desc['name']['language']
    nodocmsg = "no docstring for {0}, please file a bug report!"
    pars = ', '.join([ts.cython_cytype(p) for p in desc['parents']])
    d = {'parents': '('+pars+')' if pars else '',
         'namespace': desc['namespace'],
         }
    srcname = desc['name']['srcname']
    name = desc['name']['tarname']
    d['name'] = ts.cython_classname(name)[1]
    class_doc = desc.get('docstrings', {}).get('class', nodocmsg.format(desc['name']))
    d['class_docstring'] = indent('\"\"\"{0}\"\"\"'.format(class_doc))

    class_ctype = ts.cython_ctype(name)
    inst_name = "(<{0} *> self._inst)".format(class_ctype)

    import_tups = set()
    cimport_tups = set()
    for parent in desc['parents']:
        ts.cython_import_tuples(parent, import_tups)
        ts.cython_cimport_tuples(parent, cimport_tups)

    cdefattrs = []
    mc = desc.get('extra', {}).get('max_callbacks', max_callbacks)

    alines = []
    pdlines = []
    fplines = []
    cached_names = []
    attritems = sorted(desc['attrs'].items())
    for aname, atype in attritems:
        if aname.startswith('_'):
            continue  # skip private
        adoc = desc.get('docstrings', {}).get('attrs', {})\
                                         .get(aname, nodocmsg.format(aname))
        if ts.isfunctionpointer(atype):
            alines += _gen_function_pointer_property(aname, atype, ts, adoc,
                        cached_names=cached_names, inst_name=inst_name,
                        classname=name, max_callbacks=mc)
            fplines += _gen_function_pointer_wrapper(aname, atype, ts,
                        max_callbacks=mc, classname=name)
            pdlines.append("self._{0}_vtab_i = {1}".format(aname, mc+1))
        else:
            alines += _gen_property(aname, atype, ts, adoc, cached_names=cached_names,
                                    inst_name=inst_name, classes=classes)
        ts.cython_import_tuples(atype, import_tups)
        ts.cython_cimport_tuples(atype, cimport_tups)
    if len(fplines) > 0:
        fplines.append("_MAX_CALLBACKS_{0} = {1}".format(name, mc))
    d['attrs_block'] = indent(alines)
    d['function_pointer_block'] = "\n".join(fplines)
    pdlines += ["{0} = None".format(n) for n in cached_names]
    d['property_defaults'] = indent(indent(pdlines, join=False))

    mlines = []
    clines = []
    methcounts = _count0(desc['methods'])
    currcounts = dict([(k, 0) for k in methcounts])
    mangled_mnames = {}
    mitems = list(desc['methods'].items())
    methitems = sorted(x for x in mitems if isinstance(x[0][0], basestring))
    methitems += sorted(x for x in mitems if not isinstance(x[0][0], basestring))
    for mkey, mval in methitems:
        mname, margs = mkey[0], mkey[1:]
        mrtn = mval['return']
        mdefs = mval['defaults']
        mbasename = mname if isinstance(mname, basestring) else mname[0]
        mcyname = ts.cython_funcname(mname)
        if mbasename.startswith('_'):
            continue  # skip private
        if any([a[1] is None or a[1][0] is None for a in margs]):
            continue
        if 1 < methcounts[mname]:
            mname_mangled = "_{0}_{1}_{2:0{3}}".format(d['name'], mcyname,
                    currcounts[mname], int(math.log(methcounts[mname], 10)+1)).lower()
        else:
            mname_mangled = mcyname
        currcounts[mname] += 1
        mangled_mnames[mkey] = mname_mangled
        for a in margs:
            ts.cython_import_tuples(a[1], import_tups)
            ts.cython_cimport_tuples(a[1], cimport_tups)
        minst_name, mcname = _method_instance_names(desc, classes, mkey, mrtn, ts)
        if mcname != d['name']:
            ts.cython_import_tuples(mcname, import_tups)
            ts.cython_cimport_tuples(mcname, cimport_tups)
        if mrtn is None:
            # this must be a constructor
            if mname not in (d['name'], '__init__',
              srcname if isinstance(srcname, basestring) else srcname[:-1]):
                continue  # skip destuctors
            if 1 == methcounts[mname]:
                mname_mangled = '__init__'
                mangled_mnames[mkey] = mname_mangled
            mdoc = desc.get('docstrings', {}).get('methods', {}).get(mname, '')
            mdoc = _doc_add_sig(mdoc, mcyname, margs, mdefs)
            construct = desc['construct']
            if construct in ('struct', 'union'):
                cimport_tups.add(('libc.stdlib', 'malloc'))
            clines += _gen_constructor(mcyname, mname_mangled, d['name'], margs,
                        mdefs, ts, doc=mdoc,
                        srcpxd_filename=desc['srcpxd_filename'],
                        inst_name=minst_name, construct=construct,
                        src_lang=src_lang)
            if 1 < methcounts[mname] and currcounts[mname] == methcounts[mname]:
                # write dispatcher
                nm = {}
                for k, v in mangled_mnames.items():
                    if isinstance(k[0], basestring) and k[0] == mbasename:
                        nm[k] = v
                    elif k[0] == srcname[:-1]:
                        nm[k] = v
                clines += _gen_dispatcher('__init__', nm, ts, doc=mdoc, hasrtn=False)
        else:
            # this is a normal method
            ts.cython_import_tuples(mrtn, import_tups)
            ts.cython_cimport_tuples(mrtn, cimport_tups)
            mdoc = desc.get('docstrings', {}).get('methods', {})\
                                             .get(mname, nodocmsg.format(mname))
            mdoc = _doc_add_sig(mdoc, mcyname, margs, mdefs)
            mlines += _gen_function(mcyname, mname_mangled, margs, mrtn, mdefs,
                                    ts, mdoc, inst_name=minst_name,
                                    is_method=True)
            if 1 < methcounts[mname] and currcounts[mname] == methcounts[mname]:
                # write dispatcher
                nm = dict([(k, v) for k, v in mangled_mnames.items() \
                           if k[0] == mbasename])
                mlines += _gen_dispatcher(mcyname, nm, ts, doc=mdoc)
    if 0 == len(desc['methods']) or 0 == len(clines):
        # provide a default constructor
        mdocs = desc.get('docstrings', {}).get('methods', {})
        mdoc = mdocs.get(desc['name']['tarname'], False) or mdocs.get('__init__', '')
        attrsargs = [(Arg.LIT, "None")] * len(attritems)
        mdoc = _doc_add_sig(mdoc, '__init__', attritems, attrsargs)
        clines += _gen_default_constructor(desc, attritems, ts, doc=mdoc)
        cimport_tups.add(('libc.stdlib', 'malloc'))
    if not desc['parents']:
        clines += ["def __dealloc__(self):"]
        clines += indent("if self._free_inst and self._inst is not NULL:", join=False)
        clines += indent(indent("free(self._inst)", join=False), join=False)
        cimport_tups.add(('libc.stdlib', 'free'))

    # Add dispatcher for templated methods
    template_meths = _template_method_names(desc['methods'])
    template_dispatcher = _gen_template_func_dispatcher(template_meths, ts)
    mlines.append(indent(template_dispatcher))

    d['methods_block'] = indent(mlines)
    d['constructor_block'] = indent(clines)

    d['extra'] = desc.get('extra', {}).get('pyx', '')
    d['cdefattrs'] = indent(cdefattrs)
    pyx = _pyx_class_template.format(**d)
    if 'pyx_filename' not in desc:
        desc['pyx_filename'] = '{0}.pyx'.format(d['name'].lower())
    return import_tups, cimport_tups, pyx

def varpyx(desc, ts=None):
    """Generates a ``*.pyx`` Cython wrapper implementation for exposing a C/C++
    variable based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        Variable description dictonary.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    pyx : str
        Cython ``*.pyx`` implementation as in-memory string.

    """
    ts = ts or TypeSystem()
    nodocmsg = "no docstring for {0}, please file a bug report!"
    inst_name = desc['extra']['srcpxd_filename'].rsplit('.', 1)[0]
    import_tups = set()
    cimport_tups = set(((inst_name,),))
    name = desc['name']
    t = ts.canon(desc['type'])

    vlines = []
    if ts.isenum(t):
        doc = '"' * 3 +  "{0} is enumeration {1} of {2}" + '"' * 3
        for ename, val in t[1][2][2]:
            vlines.append("{0} = {1}.{0}".format(ename, inst_name))
            vlines.append(doc.format(ename, val, name))
            vlines.append("")
    else:
        decl, body, rtn, iscached = ts.cython_c2py(name, t, view=False, cached=False,
                                                inst_name=inst_name)
        vlines.append(decl)
        vlines.append(body)
        vlines.append(name + " = " + rtn)
        docstring = desc.get('docstring', None)
        if docstring is None:
            docstring = '"' * 3 + nodocmsg.format(name) + '"' * 3
        vlines.append(docstring)

    vlines.append(desc.get('extra', {}).get('pyx', ''))
    pyx = '\n'.join(vlines)
    extra = desc['extra']
    if 'pyx_filename' not in extra:
        extra['pyx_filename'] = '{0}.pyx'.format(desc['name']['tarbase'])
    return import_tups, cimport_tups, pyx


def funcpyx(desc, ts=None):
    """Generates a ``*.pyx`` Cython wrapper implementation for exposing a C/C++
    function based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        function description dictonary.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    pyx : str
        Cython ``*.pyx`` implementation as in-memory string.

    """
    ts = ts or TypeSystem()
    nodocmsg = "no docstring for {0}, please file a bug report!"
    inst_name = desc['extra']['srcpxd_filename'].rsplit('.', 1)[0]

    import_tups = set()
    cimport_tups = set(((inst_name,),))

    # For renaming
    ftopname = desc['name']['tarname']
    fcytopname = ts.cython_funcname(ftopname)

    flines = []
    funccounts = _count0(desc['signatures'])
    currcounts = dict([(k, 0) for k in funccounts])
    mangled_fnames = {}
    funcitems = sorted(desc['signatures'].items())
    for fkey, fval in funcitems:
        fname, fargs = fkey[0], fkey[1:]
        frtn = fval['return']
        fdefs = fval['defaults']
        fbasename = fname if isinstance(fname, basestring) else fname[0]
        #fcppname = ts.cpp_funcname(fname)
        fcyname = ts.cython_funcname(fname)
        if fbasename.startswith('_'):
            continue  # skip private
        if any([a[1] is None or a[1][0] is None for a in fargs + (frtn,)]):
            continue
        if 1 < funccounts[fname]:
            fname_mangled = "_{0}_{1:0{2}}".format(fcytopname, currcounts[fname],
                                        int(math.log(funccounts[fname], 10)+1)).lower()
        else:
            fname_mangled = fcytopname
        currcounts[fname] += 1
        mangled_fnames[fkey] = fname_mangled
        for a in fargs:
            ts.cython_import_tuples(a[1], import_tups)
            ts.cython_cimport_tuples(a[1], cimport_tups)
        ts.cython_import_tuples(frtn, import_tups)
        ts.cython_cimport_tuples(frtn, cimport_tups)
        fdoc = desc.get('docstring', nodocmsg.format(fcyname))
        fdoc = _doc_add_sig(fdoc, fcyname, fargs, fdefs, ismethod=False)
        flines += _gen_function(fcyname, fname_mangled, fargs, frtn, fdefs, ts,
                                fdoc, inst_name=inst_name, is_method=False)
        if 1 < funccounts[fname] and currcounts[fname] == funccounts[fname]:
            # write dispatcher
            nm = dict([(k, v) for k, v in mangled_fnames.items() if k[0] == fname])
            flines += _gen_dispatcher(fcytopname, nm, ts, doc=fdoc, is_method=False)
    flines.append(desc.get('extra', {}).get('pyx', ''))
    pyx = '\n'.join(flines)
    extra = desc['extra']
    if 'pyx_filename' not in extra:
        extra['pyx_filename'] = '{0}.pyx'.format(extra['name']['tarbase'])
    return import_tups, cimport_tups, pyx

#
# Plugin
#

class XDressPlugin(Plugin):
    """The main cython generator plugin.
    """

    requires = ('xdress.autodescribe',)
    """This plugin requires autodescribe."""

    defaultrc = {'max_callbacks': 8}

    rcdocs = {
        "max_callbacks": "The maximum number of callbacks for function pointers",
        }

    def update_argparser(self, parser):
        parser.add_argument('--max-callbacks', type=int, dest="max_callbacks",
                    help=self.rcdocs["max_callbacks"])

    def setup(self, rc):
        if rc.max_callbacks < 1:
            raise ValueError("max_callbacks must be greater than or equal to 1")
        if cython_version is None:
            warnings.warn('cython does not seem to be installed', RuntimeWarning)
        elif cython_version_info[:2] <= (0, 17):
            warnings.warn('cython code generated by xdress requires cython v0.18+, '
                          'cython version {0} found'.format(cython_version),
                          RuntimeWarning)

    def execute(self, rc):
        print("cythongen: creating C/C++ API wrappers")
        env = rc.env
        classes = {}
        for modname, mod in env.items():
            for name, desc in mod.items():
                if isclassdesc(desc):
                    classes[name] = desc

        # generate all files
        cpppxds = gencpppxd(env, ts=rc.ts)
        pxds = genpxd(env, classes, ts=rc.ts, max_callbacks=rc.max_callbacks)
        pyxs = genpyx(env, classes, ts=rc.ts, max_callbacks=rc.max_callbacks)

        # write out all files
        for key, cpppxd in cpppxds.items():
            newoverwrite(cpppxd, os.path.join(rc.packagedir,
                         env[key]['srcpxd_filename']), rc.verbose)
        for key, pxd in pxds.items():
            newoverwrite(pxd, os.path.join(rc.packagedir,
                         env[key]['pxd_filename']), rc.verbose)
        for key, pyx in pyxs.items():
            newoverwrite(pyx, os.path.join(rc.packagedir,
                         env[key]['pyx_filename']), rc.verbose)


#
# Misc Helpers Below
#

def _format_ns(desc):
    ns = desc.get('namespace', None)
    if ns is None:
        return ""
    elif len(ns) == 0:
        return ""
    else:
        return 'namespace "{0}"'.format(ns)

def _format_alias(desc, ts):
    ns = desc.get('namespace', None)
    cpp_name = ts.cpp_type(desc['name']['tarname'])
    if ns is None or len(ns) == 0:
        return '"{0}"'.format(cpp_name)
    else:
        return '"{0}::{1}"'.format(ns, cpp_name)

def _mangle_function_pointer_name(name, classname):
    pyref = "_xdress_{0}_{1}_proxy".format(classname, name)
    cref = pyref + "_func"
    return pyref, cref

def _isclassptr(t, classes):
    return (not isinstance(t, basestring) and t[1] == '*' and
            isinstance(t[0], basestring) and t[0] in classes)

def _isclassdblptr(t, classes):
    if 2 != len(t):
        return False
    return _isclassptr(t[0], classes) and t[1] == '*'

_exc_c_base = frozenset(['int16', 'int32', 'int64', 'int128',
                         'float32', 'float64', 'float128'])

_exc_ptr_matcher = TypeMatcher((MatchAny, '*'))

def _exception_str(exceptions, lang, rtntype, ts):
    if not exceptions:
        return ""
    if isinstance(exceptions, basestring):
        return "except " + exceptions

    if lang == 'c':
        if rtntype is None:
            return "except -1"  # helpful when we accidentally mis-guessed C for C++
        rtntype = ts.canon(rtntype)
        return ""
        # The following does not work in general since valid vs invalid returns
        # cannot be known.
        #if rtntype in _exc_c_base:
        #    return "except -1"
        #elif ts.isenum(rtntype):
        #    return "except -1"
        #elif _exc_ptr_matcher.matches(rtntype):
        #    return "except -1"
        #else:
        #    return ""
    elif lang == 'c++':
        return "except +"
    else:
        return ""

def _template_method_names(methods):
    methnames = set()
    for sig, val in methods.items():
        name = sig[0]
        if isinstance(name, basestring):
            continue
        elif val['return'] is None:  # ignore constructor / destructors
            continue
        methnames.add(name)
    return methnames

def _template_funcnames_in_mod(mod):
    funcnames = set()
    for name, desc in mod.items():
        if isinstance(name, basestring) or not isfuncdesc(desc):
            continue
        funcnames.add(name)
    return funcnames

def _classnames_in_mod(mod, ts):
    classnames = set()
    for name, desc in mod.items():
        if not isclassdesc(desc):
            continue
        classnames.add(name)
        classnames.add(ts.basename(name))
        classnames.add(desc['type'])
    return classnames

def _template_classnames_in_mod(mod):
    classnames = set()
    for name, desc in mod.items():
        if isinstance(name, basestring) or not isclassdesc(desc):
            continue
        classnames.add(name)
    return classnames

"""Generates a Cython wrappers from description dictionaries.
This module relies heavily on the type system to convert between C/C++, Cython, and
Python types in a seamless way.  While this module does not explicitly rely on the
auto-describer, it sure helps!  The functions in this module are conceptually
easy to understand -- given class descriptions they generate strings of Cython
code -- their implementations do a lot of heavy lifting.

This module is available as an xdress plugin by the name ``xdress.cythongen``.
Note that while the module does not rely on the autodescriber, the plugin does.

:author: <NAME> <<EMAIL>>

Cython Generation API
=====================
"""
from __future__ import print_function
import os
import sys
import math
import warnings
from numbers import Number

from .plugins import Plugin
from .types.matching import TypeMatcher, MatchAny
from .types.system import TypeSystem
from .utils import indent, expand_default_args, isclassdesc, isfuncdesc, \
    isvardesc, newoverwrite, sortedbytype, _lang_exts, Arg
from .version import cython_version, cython_version_info

if sys.version_info[0] >= 3:
    basestring = str

MATCH_REF = TypeMatcher((MatchAny, '&'))

AUTOGEN_WARNING = \
"""################################################
#                 WARNING!                     #
# This file has been auto-generated by xdress. #
# Do not modify!!!                             #
#                                              #
#                                              #
#                    Come on, guys. I mean it! #
################################################
"""

def gencpppxd(env, exceptions=True, ts=None):
    """Generates all cpp_*.pxd Cython header files for an environment of modules.

    Parameters
    ----------
    env : dict
        Environment dictonary mapping target module names to module description
        dictionaries.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cpppxds : dict
        Maps environment target names to Cython cpp_*.pxd header files strings.

    """
    ts = ts or TypeSystem()
    cpppxds = {}
    for name, mod in env.items():
        if mod['srcpxd_filename'] is None:
            continue
        cpppxds[name] = modcpppxd(mod, exceptions, ts=ts)
    return cpppxds

def _addotherclsnames(t, classes, name, others, ts):
    if t is None or t == (None, '*'):
        return
    spt = ts.strip_predicates(t)
    if spt in classes:
        others[name].add(spt)
    elif ts.isfunctionpointer(spt):
        for subt in spt[1:]:
            spsubt = ts.strip_predicates(subt)
            if spsubt in classes:
                others[name].add(spsubt)

def cpppxd_sorted_names(mod, ts):
    """Sorts the variable names in a cpp_*.pxd module so that C/C++
    declarations happen in the proper order.
    """
    classes = set([name for name, desc in mod.items() if isclassdesc(desc)])
    clssort = sorted(c for c in classes if isinstance(c, basestring))
    clssort += sorted(c for c in classes if not isinstance(c, basestring))
    othercls = {}
    for name in clssort:
        desc = mod[name]
        othercls[name] = set()
        for pitem in desc['parents']:
            _addotherclsnames(pitem, classes, name, othercls, ts)
        for aname, atype in desc['attrs'].items():
            _addotherclsnames(atype, classes, name, othercls, ts)
        for mkey, mval in desc['methods'].items():
            mname, margs = mkey[0], mkey[1:]
            _addotherclsnames(mval['return'], classes, name, othercls, ts)
            for marg in margs:
                _addotherclsnames(marg[1], classes, name, othercls, ts)
    clssort.sort(key=lambda x: len(othercls[x]))
    names = clssort[:1]
    for name in clssort[1:]:
        if name in names:
            continue
        for i, n in enumerate(names[:]):
            if name in othercls[n]:
                names.insert(i, name)
                break
            if othercls[name] <= set(names[:i+1] + [name]):
                names.insert(i+1, name)
                break
        else:
            names.append(name)
    names += sortedbytype([name for name, desc in mod.items() if isvardesc(desc)])
    names += sortedbytype([name for name, desc in mod.items() if isfuncdesc(desc)])
    return names


def modcpppxd(mod, exceptions=True, ts=None):
    """Generates a cpp_*.pxd Cython header file for exposing a C/C++ module to
    other Cython wrappers based off of a dictionary description of the module.

    Parameters
    ----------
    mod : dict
        Module description dictonary.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cpppxd : str
        Cython cpp_*.pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    m = {'extra': mod.get('extra', ''),
         "srcpxd_filename": mod.get("srcpxd_filename", "")}
    attrs = []
    cimport_tups = set()
    classnames = _classnames_in_mod(mod, ts)
    with ts.local_classes(classnames, frozenset(['c'])):
        for name in cpppxd_sorted_names(mod, ts):
            desc = mod[name]
            incfiles = desc['name']['incfiles']
            if 0 == len(incfiles):
                msg = "cythongen requires an include file for {0}, none found"
                raise ValueError(msg.format(name))
            elif 1 < len(incfiles):
                msg = "multiple include files found for {0}, choosing the first: {1}"
                warnings.warn(msg.format(name, incfiles[0]), RuntimeWarning)
            if isvardesc(desc):
                ci_tup, attr_str = varcpppxd(desc, exceptions, ts)
            elif isfuncdesc(desc):
                ci_tup, attr_str = funccpppxd(desc, exceptions, ts)
            elif isclassdesc(desc):
                ci_tup, attr_str = classcpppxd(desc, exceptions, ts)
            else:
                continue
            cimport_tups |= ci_tup
            attrs.append(attr_str)
    if mod.get('language', None) == 'c':
        cimport_tups.discard((ts.stlcontainers,))
    m['cimports'] = "\n".join(sorted(ts.cython_cimport_lines(cimport_tups)))
    m['attrs_block'] = "\n".join(attrs)
    t = '\n\n'.join([AUTOGEN_WARNING, '{cimports}', '{attrs_block}', '{extra}'])
    cpppxd = t.format(**m)
    return cpppxd

_cpppxd_var_template = \
"""# function signatures
cdef extern from "{header_filename}" {namespace}:

{variables_block}

{extra}
"""

def varcpppxd(desc, exceptions=True, ts=None):
    """Generates a cpp_*.pxd Cython header snippet for exposing a C/C++ variable
    to other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        Function description dictonary.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cimport_tups : set of tuples
        Set of Cython cimport tuples for cpp_*.pxd header file.
    cpppxd : str
        Cython cpp_*.pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    t = ts.canon(desc['type'])
    d = {'name': desc['name']['tarname'],
         'header_filename':  desc['name']['incfiles'][0],
         'namespace': _format_ns(desc),
         }

    inc = set(['c'])
    cimport_tups = set()
    ts.cython_cimport_tuples(t, cimport_tups, inc)

    vlines = []
    if ts.isenum(t):
        vlines.append("cdef enum {0}:".format(d['name']))
        enames = [name for name, val in t[1][2][2]]
        vlines += indent(enames, 4, join=False)
    else:
        ct = ts.cython_ctype(t)
        vlines.append("{0} {1}".format(ct, d['name']))
    d['variables_block'] = indent(vlines, 4)
    if 0 == len(d['variables_block'].strip()):
        return set(), ''

    d['extra'] = desc.get('extra', {}).get('cpppxd', '')
    cpppxd = _cpppxd_var_template.format(**d)
    extra = desc['extra']
    if 'srcpxd_filename' not in extra:
        ext = _lang_exts[name.language]
        extra['srcpxd_filename'] = '{0}_{1}.pxd'.format(ext, d['name']['tarbase'])
    return cimport_tups, cpppxd

_cpppxd_func_template = \
"""# function signatures
cdef extern from "{header_filename}" {namespace}:

{functions_block}

{extra}
"""

def funccpppxd(desc, exceptions=True, ts=None):
    """Generates a cpp_*.pxd Cython header snippet for exposing a C/C++ function
    to other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        Function description dictonary.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cimport_tups : set of tuples
        Set of Cython cimport tuples for cpp_*.pxd header file.
    cpppxd : str
        Cython cpp_*.pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    d = {'name': desc['name']['tarname'],
         'header_filename':  desc['name']['incfiles'][0],
         'namespace': _format_ns(desc),
         }
    inc = set(['c'])
    cimport_tups = set()

    flines = []
    funcitems = sorted(expand_default_args(desc['signatures'].items()))
    for fkey, frtn in funcitems:
        fname, fargs = fkey[0], fkey[1:]
        fbasename = fname if isinstance(fname, basestring) else fname[0]
        cppname = ts.cpp_funcname(fname)
        cyname = ts.cython_funcname(fname)
        if fbasename.startswith('_'):
            continue  # private
        if any([a[1] is None or a[1][0] is None for a in fargs + (frtn,)]):
            continue
        argfill = ", ".join([ts.cython_ctype(a[1]) for a in fargs])
        for a in fargs:
            ts.cython_cimport_tuples(a[1], cimport_tups, inc)
        estr = _exception_str(exceptions, desc['name']['language'], frtn, ts)
        if fname == cppname == cyname:
            line = "{0}({1}) {2}".format(fname, argfill, estr)
        else:
            line = '{0} "{1}" ({2}) {3}'.format(cyname, cppname, argfill, estr)
        rtype = ts.cython_ctype(frtn)
        ts.cython_cimport_tuples(frtn, cimport_tups, inc)
        line = rtype + " " + line
        if line not in flines:
            flines.append(line)
    d['functions_block'] = indent(flines, 4)
    if 0 == len(d['functions_block'].strip()):
        return set(), ''

    d['extra'] = desc.get('extra', {}).get('cpppxd', '')
    cpppxd = _cpppxd_func_template.format(**d)
    extra = desc['extra']
    if 'srcpxd_filename' not in extra:
        extra['srcpxd_filename'] = '{0}_{1}.pxd'.format(d['name']['tarbase'])
    return cimport_tups, cpppxd

_cpppxd_class_template = \
"""cdef extern from "{header_filename}" {namespace}:

    cdef {construct_kind} {name}{alias}{parents}:
        # constructors
{constructors_block}

        # attributes
{attrs_block}

        # methods
{methods_block}
        pass

{extra}
"""

def classcpppxd(desc, exceptions=True, ts=None):
    """Generates a cpp_*.pxd Cython header snippet for exposing a C/C++ class or
    struct to other Cython wrappers based off of a dictionary description of the
    class or struct.

    Parameters
    ----------
    desc : dict
        Class description dictonary.
    exceptions : bool or str, optional
        Cython exception annotation.  Set to True to automatically detect exception
        types, False for when exceptions should not be included, and a str (such as
        '+' or '-1') to apply to everywhere.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    cimport_tups : set of tuples
        Set of Cython cimport tuples for cpp_*.pxd header file.
    cpppxd : str
        Cython cpp_*.pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    src_lang = desc['name']['language']
    pars = ', '.join([ts.cython_ctype(p) for p in desc['parents']])
    d = {'parents': '('+pars+')' if pars else '',
         'header_filename': desc['name']['incfiles'][0],}
    d['namespace'] = _format_ns(desc)
    name = desc['name']['tarname']
    if isinstance(desc['type'], basestring):
        d['name'] = ts.cython_ctype(name)
        d['alias'] = ''
    else:
        d['name'] = ts.cython_classname(name)[1]
        d['alias'] = ' ' + _format_alias(desc, ts)

    construct_kinds = {'union': 'union'}
    if src_lang == 'c++':
        construct_kinds.update({'struct': 'cppclass', 'class': 'cppclass'})
        d['construct_kind'] = construct_kinds[desc.get('construct', 'class')]
    else:  # C
        construct_kinds.update({'struct': 'struct'})
        d['construct_kind'] = construct_kinds[desc.get('construct', 'struct')]
    inc = set(['c'])

    cimport_tups = set()
    for parent in desc['parents']:
        ts.cython_cimport_tuples(parent, cimport_tups, inc)

    alines = []
    attritems = sorted(desc['attrs'].items())
    for aname, atype in attritems:
        if aname.startswith('_'):
            continue
        actype = ts.cython_ctype(atype)
        if '{type_name}' in actype:
            aline = actype.format(type_name=aname)
        else:
            aline = "{0} {1}".format(actype, aname)
        alines.append(aline)
        ts.cython_cimport_tuples(atype, cimport_tups, inc)
    d['attrs_block'] = indent(alines, 8)

    mlines = []
    clines = []
    dargs = expand_default_args(desc['methods'].items())
    methitems = sorted(x for x in dargs if isinstance(x[0][0], basestring))
    methitems += sorted(x for x in dargs if not isinstance(x[0][0], basestring))
    default_constructor =  ((d['name'],),  None)
    if d['construct_kind'] == 'cppclass' and default_constructor not in methitems:
        methitems.insert(0, default_constructor)

    for mkey, mrtn in methitems:
        mname, margs = mkey[0], mkey[1:]
        mbasename = mname if isinstance(mname, basestring) else mname[0]
        mcppname = ts.cpp_funcname(mname)
        mcyname = ts.cython_funcname(mname)
        if mbasename.startswith('_') or mbasename.startswith('~'):
            continue  # private or destructor
        argfill = ", ".join([ts.cython_ctype(a[1]) for a in margs])
        for a in margs:
            ts.cython_cimport_tuples(a[1], cimport_tups, inc)
        estr = _exception_str(exceptions, src_lang, mrtn, ts)
        if mname == mcppname == mcyname:
            line = "{0}({1}) {2}".format(mname, argfill, estr)
        else:
            line = '{0} "{1}" ({2}) {3}'.format(mcyname, mcppname, argfill, estr)
        if mrtn is None:
            # this must be a constructor
            line = "{0}({1}) {2}".format(d['name'], argfill, estr)
            if line not in clines:
                clines.append(line)
        else:
            # this is a normal method
            if MATCH_REF.matches(mrtn):
                mrtn = mrtn[0]
            rtype = ts.cython_ctype(mrtn)
            ts.cython_cimport_tuples(mrtn, cimport_tups, inc)
            line = rtype + " " + line
            if line not in mlines:
                mlines.append(line)
    d['methods_block'] = indent(mlines, 8)
    d['constructors_block'] = indent(clines, 8) if src_lang == 'c++' else ''

    d['extra'] = desc.get('extra', {}).get('cpppxd', '')
    cpppxd = _cpppxd_class_template.format(**d)

    extra = desc['extra']
    if 'srcpxd_filename' not in desc:
        desc['srcpxd_filename'] = extra['srcpxd_filename']

    return cimport_tups, cpppxd


def genpxd(env, classes=(), ts=None, max_callbacks=8):
    """Generates all pxd Cython header files for an environment of modules.

    Parameters
    ----------
    env : dict
        Environment dictonary mapping target module names to module description
        dictionaries.
    classes : sequence, optional
        Listing of all class names that are handled by cythongen.  This may be
        the same dictionary as in genpyx()
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pxds : str
        Maps environment target names to Cython pxd header files strings.

    """
    ts = ts or TypeSystem()
    pxds = {}
    for name, mod in env.items():
        if mod['pxd_filename'] is None:
            continue
        pxds[name] = modpxd(mod, classes, ts=ts, max_callbacks=max_callbacks)
    return pxds

def pxd_sorted_names(mod):
    """Sorts the names in a module to make sure that pxd declarations happen in the
    proper order."""
    classnames = []
    othernames = []
    for name, desc in mod.items():
        if isclassdesc(desc):
            if name in classnames:
                continue
            parents = desc['parents']
            if not parents:
                classnames.insert(0, name)
                continue
            for parent in parents:
                if parent not in classnames and parent in mod:
                    classnames.append(parent)
            classnames.append(name)
        else:
            othernames.append(name)
    names = classnames + sortedbytype(othernames)
    return names


def modpxd(mod, classes=(), ts=None, max_callbacks=8):
    """Generates a pxd Cython header file for exposing C/C++ data to
    other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    mod : dict
        Module description dictonary.
    classes : sequence, optional
        Listing of all class names that are handled by cythongen.  This may be
        the same dictionary as in modpyx().
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pxd : str
        Cython .pxd header file as in-memory string.

    """
    ts = ts or TypeSystem()
    m = {'extra': mod.get('extra', ''),
         "pxd_filename": mod.get("pxd_filename", "")}
    attrs = []
    cimport_tups = set()
    classnames = _classnames_in_mod(mod, ts)
    with ts.local_classes(classnames):
        for name in pxd_sorted_names(mod):
            desc = mod[name]
            if isclassdesc(desc):
                ci_tup, attr_str = classpxd(desc, classes, ts=ts,
                                            max_callbacks=max_callbacks)
            else:
                # no need to wrap functions again
                continue
            cimport_tups |= ci_tup
            attrs.append(attr_str)
    cimport_tups.discard((mod["name"],))
    if mod.get('language', None) == 'c':
        cimport_tups.discard((ts.stlcontainers,))
    m['cimports'] = "\n".join(sorted(ts.cython_cimport_lines(cimport_tups)))
    m['attrs_block'] = "\n".join(attrs)
    t = '\n\n'.join([AUTOGEN_WARNING, '{cimports}', '{attrs_block}', '{extra}'])
    pxd = t.format(**m)
    return pxd


_pxd_class_template = \
"""{function_pointer_block}

cdef class {name}{parents}:
{body}
    pass

{extra}
"""


def classpxd(desc, classes=(), ts=None, max_callbacks=8):
    """Generates a ``*pxd`` Cython header snippet for exposing a C/C++ class to
    other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        Class description dictonary.
    classes : sequence, optional
        Listing of all class names that are handled by cythongen.  This may be
        the same dictionary as in modpyx().
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    cimport_tups : set of tuples
        Set of Cython cimport tuples for .pxd header file.
    pxd : str
        Cython ``*.pxd`` header snippet for class.

    """
    ts = ts or TypeSystem()
    extra = desc['extra']
    if 'pxd_filename' not in extra:
        extra['pxd_filename'] = '{0}.pxd'.format(desc['name']['tarbase'])
    pars = ', '.join([ts.cython_cytype(p) for p in desc['parents']])
    d = {'parents': '('+pars+')' if pars else ''}
    name = desc['name']['tarname']
    d['name'] = ts.cython_classname(name)[1]
    max_callbacks = desc.get('extra', {}).get('max_callbacks', max_callbacks)
    mczeropad = int(math.log10(max_callbacks)) + 1

    cimport_tups = set()
    for parent in desc['parents']:
        ts.cython_cimport_tuples(parent, cimport_tups, set(['cy']))

    from_cpppxd = desc['srcpxd_filename'].rsplit('.', 1)[0]
    tarname = desc['name']['tarname']
    d['name_type'] = ts.cython_ctype(tarname)
    ts.cython_cimport_tuples(tarname, cimport_tups, set(['c']))

    body = [] if desc['parents'] else ['cdef void * _inst', 'cdef public bint _free_inst']
    attritems = sorted(desc['attrs'].items())
    fplines = []
    for aname, atype in attritems:
        if aname.startswith('_'):
            continue  # skip private
        _, _, cachename, iscached = ts.cython_c2py(aname, atype, cache_prefix=None)
        if iscached:
            ts.cython_cimport_tuples(atype, cimport_tups)
            if _isclassptr(atype, classes):
                atype_nopred = ts.strip_predicates(atype)
                cyt = ts.cython_cytype(atype_nopred)
            elif _isclassdblptr(atype, classes):
                cyt = 'list'
            else:
                cyt = ts.cython_cytype(atype)
            decl = "cdef public {0} {1}".format(cyt, cachename)
            body.append(decl)
        if ts.isfunctionpointer(atype):
            apyname, acname = _mangle_function_pointer_name(aname, name)
            acdecl = "cdef public " + ts.cython_ctype(('function',)+ atype[1:])
            for i in range(max_callbacks):
                suffix = "{0:0{1}}".format(i, mczeropad)
                apyname_i, acname_i = apyname + suffix, acname + suffix
                fplines.append("cdef public object " + apyname_i)
                fplines.append(acdecl.format(type_name=acname_i))
            body.append("cdef unsigned int _{0}_vtab_i".format(aname))
            fplines.append("cdef unsigned int _current_{0}_vtab_i".format(apyname))

    if len(fplines) > 0:
        fplines.append("cdef unsigned int _MAX_CALLBACKS_" + d['name'])

    d['body'] = indent(body or ['pass'])
    d['function_pointer_block'] = '\n'.join(fplines)
    d['extra'] = desc.get('extra', {}).get('pxd', '')
    pxd = _pxd_class_template.format(**d)
    return cimport_tups, pxd


def genpyx(env, classes=None, ts=None, max_callbacks=8):
    """Generates all pyx Cython implementation files for an environment of modules.

    Parameters
    ----------
    env : dict
        Environment dictonary mapping target module names to module description
        dictionaries.
    classes : dict, optional
        Dictionary which maps all class names that are required to
        their own descriptions.  This is required for resolving class heirarchy
        dependencies. If None, this will be computed here.
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pyxs : str
        Maps environment target names to Cython pxd header files strings.

    """
    ts = ts or TypeSystem()
    if classes is None:
        # get flat namespace of class descriptions
        classes = {}
        for envname, mod in env.items():
            for modname, desc in mod.items():
                if isclassdesc(desc):
                    classes[desc['name']] = desc
    # gen files
    pyxs = {}
    for name, mod in env.items():
        if mod['pyx_filename'] is None:
            continue
        pyxs[name] = modpyx(mod, classes=classes, ts=ts, max_callbacks=max_callbacks)
    return pyxs


_pyx_mod_template = AUTOGEN_WARNING + \
'''"""{docstring}
"""
{cimports}

{imports}

{attrs_block}

{extra}
'''

def modpyx(mod, classes=None, ts=None, max_callbacks=8):
    """Generates a pyx Cython implementation file for exposing C/C++ data to
    other Cython wrappers based off of a dictionary description.

    Parameters
    ----------
    mod : dict
        Module description dictonary.
    classes : dict, optional
        Dictionary which maps all class names that are required to
        their own descriptions.  This is required for resolving class heirarchy
        dependencies.
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pyx : str
        Cython pyx header file as in-memory string.

    """
    ts = ts or TypeSystem()
    m = {'extra': mod.get('extra', ''),
         'docstring': mod.get('docstring', "no docstring, please file a bug report!"),
         "pyx_filename": mod.get("pyx_filename", "")}
    attrs = []
    import_tups = set()
    cimport_tups = set()
    classnames = _classnames_in_mod(mod, ts)
    with ts.local_classes(classnames):
        for name, desc in mod.items():
            if isvardesc(desc):
                i_tup, ci_tup, attr_str = varpyx(desc, ts=ts)
            elif isfuncdesc(desc):
                i_tup, ci_tup, attr_str = funcpyx(desc, ts=ts)
            elif isclassdesc(desc):
                i_tup, ci_tup, attr_str = classpyx(desc, classes=classes, ts=ts,
                                                   max_callbacks=max_callbacks)
            else:
                continue
            import_tups |= i_tup
            cimport_tups |= ci_tup
            attrs.append(attr_str)
        # Add dispatcher for template functions
        template_funcs = _template_funcnames_in_mod(mod)
        template_dispatcher = _gen_template_func_dispatcher(template_funcs, ts)
        attrs.append(template_dispatcher)
        # Add dispatcher for template classes
        template_classes = _template_classnames_in_mod(mod)
        template_dispatcher = _gen_template_class_dispatcher(template_classes, ts)
        attrs.append(template_dispatcher)
    import_tups.discard((mod["name"],))
    #cimport_tups.discard((mod["name"],))  # remain commented for decls
    if mod.get('language', None) == 'c':
        import_tups.discard((ts.stlcontainers,))
        cimport_tups.discard((ts.stlcontainers,))
    m['imports'] = "\n".join(sorted(ts.cython_import_lines(import_tups)))
    m['cimports'] = "\n".join(sorted(ts.cython_cimport_lines(cimport_tups)))
    if 'numpy' in m['cimports']:
        m['imports'] += "\n\nnp.import_array()"
    m['attrs_block'] = "\n".join(attrs)
    t = '\n\n'.join([AUTOGEN_WARNING, '{cimports}', '{attrs_block}', '{extra}'])
    pyx = _pyx_mod_template.format(**m)
    return pyx

def _gen_template_pyfill(arg, kind, ts):
    """Generates the fill values for an argument of a type into a template type t.
    """
    if kind is Arg.TYPE:
        rtn = ts.cython_pytype(arg)
    elif kind is Arg.LIT:
        rnt = str(arg)
    elif kind is Arg.VAR:
        rtn = arg
    elif isinstance(arg, Number):
        rtn = str(arg)
    elif isinstance(arg, basestring):
        try:
            rtn = ts.cython_pytype(arg)
        except TypeError:
            rtn = arg
    return rtn

def _gen_template_func_dispatcher(templates, ts):
    """Generates a dictionary-based dispacher for template functions.
    """
    if 0 == len(templates):
        return ""
    templates = sorted(templates)
    disp = ['', "#", "# Function Dispatchers", "#",]
    alreadyinitd = set()
    for t in templates:
        initline = "{0} = {{}}".format(t[0])
        if initline not in alreadyinitd:
            disp.append("")
            disp.append("# {0} dispatcher".format(t[0]))
            disp.append(initline)
            alreadyinitd.add(initline)
        args = t[1:]
        pytype = ts.cython_funcname(t)
        kinds = ts.argument_kinds.get(t, ((Arg.NONE,))*(len(t)-1))
        if 0 == len(args):
            raise ValueError("type {0!r} not a template".format(t))
        elif 1 == len(args):
            disp.append("{0}[{1!r}] = {2}".format(t[0], t[1], pytype))
            disp.append("{0}[{1}] = {2}".format(t[0],
                _gen_template_pyfill(t[1], kinds[0], ts), pytype))
        else:
            rs = [repr(_) for _ in t[1:]]
            pyts = [_gen_template_pyfill(x, k, ts) for x, k in zip(t[1:], kinds)]
            disp.append("{0}[{1}] = {2}".format(t[0], ", ".join(rs), pytype))
            disp.append("{0}[{1}] = {2}".format(t[0], ", ".join(pyts), pytype))
    return "\n".join(disp)

def _gen_template_class_dispatcher(templates, ts):
    """Generates a dictionary-based dispacher for template classes.
    """
    if 0 == len(templates):
        return ""
    templates = sorted(templates)
    disp = ['', "#", "# Class Dispatchers", "#",]
    alreadyinitd = set()
    for t in templates:
        initline = "{0} = {{}}".format(t[0])
        if initline not in alreadyinitd:
            disp.append("")
            disp.append("# {0} Dispatcher".format(t[0]))
            disp.append(initline)
            alreadyinitd.add(initline)
        args = t[1:-1]
        pytype = ts.cython_pytype(t)
        kinds = ts.argument_kinds.get(t, ((Arg.NONE,))*(len(t)-1))
        if 0 == len(args):
            raise ValueError("type {0!r} not a template".format(t))
        elif 1 == len(args):
            disp.append("{0}[{1!r}] = {2}".format(t[0], t[1], pytype))
            disp.append("{0}[{1}] = {2}".format(t[0],
                _gen_template_pyfill(t[1], kinds[0], ts), pytype))
        else:
            rs = [repr(_) for _ in t[1:-1]]
            pyts = [_gen_template_pyfill(x, k, ts) for x, k in zip(t[1:], kinds)]
            disp.append("{0}[{1}] = {2}".format(t[0], ", ".join(rs), pytype))
            disp.append("{0}[{1}] = {2}".format(t[0], ", ".join(pyts), pytype))
    return "\n".join(disp)

def _gen_property_get(name, t, ts, cached_names=None, inst_name="self._inst",
                      classes=()):
    """This generates a Cython property getter for a variable of a given
    name and type."""
    lines = ['def __get__(self):']
    decl, body, rtn, iscached = ts.cython_c2py(name, t, inst_name=inst_name)
    if decl is not None:
        if _isclassptr(t, classes):
            decl, _, _, _ = ts.cython_c2py(name, t[0], inst_name=inst_name)
        lines += indent(decl, join=False)
    if body is not None:
        lines += indent(body, join=False)
    if iscached and cached_names is not None:
        cached_names.append(rtn)
    lines += indent("return {0}".format(rtn), join=False)
    return lines

def _gen_property_set(name, t, ts, inst_name="self._inst", cached_name=None,
                      classes=()):
    """This generates a Cython property setter for a variable of a given
    name and type."""
    lines = ['def __set__(self, value):']
    decl, body, rtn = ts.cython_py2c('value', t)
    if decl is not None:
        lines += indent(decl, join=False)
    if body is not None:
        lines += indent(body, join=False)
    lines += indent("{0}.{1} = {2}".format(inst_name, name, rtn), join=False)
    if cached_name is not None:
        lines += indent("{0} = None".format(cached_name), join=False)
    return lines

def _gen_property(name, t, ts, doc=None, cached_names=None, inst_name="self._inst",
                  classes=()):
    """This generates a Cython property for a variable of a given name and type."""
    lines  = ['property {0}:'.format(name)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    oldcnlen = 0 if cached_names is None else len(cached_names)
    lines += indent(_gen_property_get(name, t, ts, cached_names=cached_names,
                    inst_name=inst_name, classes=classes), join=False)
    lines += ['']
    newcnlen = 0 if cached_names is None else len(cached_names)
    cached_name = cached_names[-1] if newcnlen == 1 + oldcnlen else None
    lines += indent(_gen_property_set(name, t, ts, inst_name=inst_name,
                    cached_name=cached_name, classes=classes), join=False)
    lines += ['', ""]
    return lines

def _gen_function_pointer_property(name, t, ts, doc=None, cached_names=None,
        inst_name="self._inst", classname='', max_callbacks=8):
    """This generates a Cython property for a function pointer variable."""
    lines  = ['property {0}:'.format(name)]

    # get section
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    oldcnlen = 0 if cached_names is None else len(cached_names)
    lines += indent(_gen_property_get(name, t, ts, cached_names=cached_names,
                                      inst_name=inst_name), join=False)

    # set section
    mczeropad = int(math.log10(max_callbacks)) + 1
    lines += [""]
    newcnlen = 0 if cached_names is None else len(cached_names)
    cached_name = cached_names[-1] if newcnlen == 1 + oldcnlen else None
    setlines = indent(_gen_property_set(name, ('void', '*'), ts, inst_name=inst_name,
                                        cached_name=cached_name), join=False)

    lines += setlines[:1]
    lines += indent(indent(['if not callable(value):',
                ('    raise ValueError("{0!r} is not callable but ' + classname +
                 '.' + name + ' is a function pointer!".format(value))')],
                join=False), join=False)
    #lines += setlines[1:]
    pyname, cname = _mangle_function_pointer_name(name, classname)
    pynames = [pyname + "{0:0{1}}".format(i, mczeropad) for i in \
                                                    range(max_callbacks)]
    cnames = [cname + "{0:0{1}}".format(i, mczeropad) for i in \
                                                    range(max_callbacks)]
    if max_callbacks == 1:
        suffix = '0'
        extraset = ('global {pyname}\n'
                    '{cached_name} = value\n'
                    '{pyname} = value\n'
                    '{inst_name}.{name} = {cname}\n'
                    ).format(name=name, pyname=pyname + suffix, cname=cname + suffix,
                             cached_name=cached_name, inst_name=inst_name)
    elif max_callbacks > 1:
        extraset = ['cdef unsigned int vtab_i',
                    '{cached_name} = value'.format(cached_name=cached_name),
                    "global " + ', '.join(pynames) + \
                                ', _current_{0}_vtab_i'.format(pyname),]
        selectlines = []
        for i, pyname_i in enumerate(pynames):
            selectlines.append("elif {0} is None:".format(pyname_i))
            selectlines.append("    vtab_i = {0}".format(i))
        selectlines[0] = selectlines[0][2:]
        extraset += selectlines
        extraset += ['else:',
                     ('    warnings.warn("Ran out of available callbacks for '
                      '{0}.{1}, overriding existing callback.", RuntimeWarning)'
                      ).format(classname, name),
                     '    vtab_i = _current_{0}_vtab_i'.format(pyname),
                     '    _current_{0}_vtab_i = (_current_{0}_vtab_i+1)%{1}'.format(
                                                            pyname, max_callbacks),
                     'self._{0}_vtab_i = vtab_i'.format(name),]
        setvallines = []
        for i, (pyname_i, cname_i) in enumerate(zip(pynames, cnames)):
            setvallines.append("elif vtab_i == {0}:".format(i))
            setvallines.append("    {pyname} = value".format(pyname=pyname_i))
            setvallines.append("    {inst_name}.{name} = {cname}".format(
                                inst_name=inst_name, name=name, cname=cname_i))
        setvallines[0] = setvallines[0][2:]
        extraset += setvallines
    else:
        msg = "The max number of callbacks for {0} must be >=1, got {1}."
        raise RuntimeError(msg.format(classname, max_callbacks))
    lines += indent(indent(extraset, join=False), join=False)
    lines.append('')
    lines += ["def _deref_{0}_callback(self):".format(name),
              '    "Warning: this can have dangerous side effects!"',
              '    cdef unsigned int vtab_i',
              '    {cached_name} = None'.format(cached_name=cached_name),
              "    if self._{0}_vtab_i < {1}:".format(name, max_callbacks+1),
              '        vtab_i = self._{0}_vtab_i'.format(name),
              "        self._{0}_vtab_i = {1}".format(name, max_callbacks+1), ]
    dereflines = []
    for i, pyname_i in enumerate(pynames):
        dereflines.append("elif vtab_i == {0}:".format(i))
        dereflines.append("    global {0}".format(pyname_i))
        dereflines.append("    {0} = None".format(pyname_i))
    dereflines[0] = dereflines[0][2:]
    lines += indent(indent(dereflines, join=False), join=False)
    lines += ['', ""]
    return lines

def _gen_function_pointer_wrapper(name, t, ts, classname='', max_callbacks=8):
    """This generates a Cython wrapper for a function pointer variable."""
    pyname, cname = _mangle_function_pointer_name(name, classname)
    mczeropad = int(math.log10(max_callbacks)) + 1
    lines = ["#\n# Function pointer helpers for {1}.{0}\n#".format(name, classname),
             "_current_{0}_vtab_i = 0".format(pyname), ""]
    for i in range(max_callbacks):
        suffix = "{0:0{1}}".format(i, mczeropad)
        pyname_i, cname_i = pyname + suffix, cname + suffix
        decl, body, rtn = ts.cython_py2c(pyname_i, t, proxy_name=cname_i)
        lines += [pyname_i + " = None", '']
        lines += rtn.splitlines()
        lines.append('')
    lines += ['', ""]
    return lines

def _gen_argfill(args, defaults):
    """Generate argument list for a function, and return (argfill, names).
    If any argument names or empty, the corresponding entry in names will
    be '_n' for some integer n.
    """
    counter = 0
    taken = frozenset(a[0] for a in args)
    names = []
    afill = []
    for (name, t), (kind, default) in zip(args, defaults):
        if not name: # Empty name, generate a fresh dummy symbol
            while 1:
                name = '_%d'%counter
                counter += 1
                if name not in taken:
                    break
        names.append(name)
        if kind is Arg.NONE:
            afillval = name
        elif kind is Arg.LIT:
            afillval = "{0}={1!r}".format(name, default)
        elif kind is Arg.VAR:
            afillval = "{0}={1}".format(name, default)
        elif kind is Arg.TYPE:
            raise ValueError("default argument value cannot be a type: "
                             "{0}".format(name))
        else:
            raise ValueError("default argument value cannot be determined: "
                             "{0}".format(name))
        afill.append(afillval)
    return ", ".join(afill), names

def _gen_function(name, name_mangled, args, rtn, defaults, ts, doc=None,
                  inst_name="self._inst", is_method=False):
    argfill, names = _gen_argfill(args, defaults)
    if is_method:
        argfill = "self, " + argfill
    lines  = ['def {0}({1}):'.format(name_mangled, argfill)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    decls = []
    argbodies = []
    argrtns = {}
    for n,a in zip(names, args):
        adecl, abody, artn = ts.cython_py2c(n, a[1])
        if adecl is not None:
            decls += indent(adecl, join=False)
        if abody is not None:
            argbodies += indent(abody, join=False)
        argrtns[n] = artn
    rtype_orig = ts.cython_ctype(rtn)
    rtype = rtype_orig.replace('const ', "").replace(' &', '')
    hasrtn = rtype not in set(['None', None, 'NULL', 'void'])
    argvals = ', '.join(argrtns[n] for n in names)
    fcall = '{0}.{1}({2})'.format(inst_name, name, argvals)
    if hasrtn:
        fcdecl, fcbody, fcrtn, fccached = ts.cython_c2py('rtnval', rtn, cached=False, view=False)
        decls += indent("cdef {0} {1}".format(rtype, 'rtnval'), join=False)
        if 'const ' in rtype_orig:
            func_call = indent('rtnval = <{0}> {1}'.format(rtype, fcall), join=False)
        else:
            func_call = indent('rtnval = {0}'.format(fcall), join=False)
        if fcdecl is not None:
            decls += indent(fcdecl, join=False)
        if fcbody is not None:
            func_call += indent(fcbody, join=False)
        func_rtn = indent("return {0}".format(fcrtn), join=False)
    else:
        func_call = indent(fcall, join=False)
        func_rtn = []
    lines += decls
    lines += argbodies
    lines += func_call
    lines += func_rtn
    lines += ['', ""]
    return lines

def _gen_default_constructor(desc, attrs, ts, doc=None, srcpxd_filename=None):
    src_lang = desc['name']['language']
    args = ['self'] + [a + "=None" for a, _ in attrs] + ['*args', '**kwargs']
    argfill = ", ".join(args)
    lines  = ['def __init__({0}):'.format(argfill)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    ct = ts.cython_ctype(desc['type'])
    if desc['construct'] == 'class':
        fcall = 'self._inst = new {0}()'.format(ct)
    elif desc['construct'] in ('struct', 'union'):
        construct_template = 'self._inst = malloc(sizeof({0}))\n'
        if src_lang == 'c++' and desc['construct'] == 'struct':
            # Only call the default constructor when it makes sense.
            construct_template += '(<{0} *> self._inst)[0] = {0}()'
        fcall = construct_template.format(ct)
    else:
        raise ValueError('construct must be either "class", "struct" or "union".')
    lines.extend(indent(fcall, join=False))
    for a, _ in attrs:
        lines.append(indent("if {0} is not None:".format(a)))
        lines.append(indent("self.{0} = {0}".format(a), 8))
    lines += ['', ""]
    return lines

def _gen_constructor(name, name_mangled, classname, args, defaults, ts,
                     doc=None, srcpxd_filename=None, inst_name="self._inst",
                     construct="class", src_lang='c++'):
    argfill, names = _gen_argfill(args, defaults)
    lines  = ['def {0}(self, {1}):'.format(name_mangled, argfill)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    decls = []
    argbodies = []
    argrtns = {}
    for n,a in zip(names, args):
        adecl, abody, artn = ts.cython_py2c(n, a[1])
        if adecl is not None:
            decls += indent(adecl, join=False)
        if abody is not None:
            argbodies += indent(abody, join=False)
        argrtns[n] = artn
    argvals = ', '.join(argrtns[n] for n in names)
    classname = classname if srcpxd_filename is None else \
                    "{0}.{1}".format(srcpxd_filename.rsplit('.', 1)[0], classname)
    if construct == 'class':
        fcall = 'self._inst = new {0}({1})'.format(classname, argvals)
    elif construct in ('struct', 'union'):
        construct_template = 'self._inst = malloc(sizeof({0}))\n'
        if src_lang == 'c++' and construct == 'struct':
            # Only call the default constructor when it makes sense.
            construct_template += '(<{0} *> self._inst)[0] = {0}({1})'
        fcall = construct_template.format(classname, argvals)
    else:
        raise ValueError('construct must be either "class", "struct", or "union".')
    func_call = indent(fcall, join=False)
    lines += decls
    lines += argbodies
    lines += func_call
    lines += ['', ""]
    return lines

def _gen_dispatcher(name, name_mangled, ts, doc=None, hasrtn=True, is_method=True):
    argfill = ", ".join(['self', '*args', '**kwargs'])
    if is_method is True:
        # string to format for arg checking
        arg_chk_str = "if types <= self.{0}_argtypes:"

        # string to format for dispatching and returning
        dispatch_str_ret = "return self.{0}(*args, **kwargs)"
        dispatch_str_no_ret = "self.{0}(*args, **kwargs)"

        # Make self a method argument or not
        argfill = ", ".join(['self', '*args', '**kwargs'])
    else:
        arg_chk_str = "if types <= {0}_argtypes:"
        dispatch_str_ret = "return {0}(*args, **kwargs)"
        dispatch_str_no_ret = "{0}(*args, **kwargs)"
        argfill = ", ".join(['*args', '**kwargs'])
    lines  = ['def {0}({1}):'.format(name, argfill)]
    lines += [] if doc is None else indent('\"\"\"{0}\"\"\"'.format(doc), join=False)
    types = ["types = set([(i, type(a)) for i, a in enumerate(args)])",
             "types.update([(k, type(v)) for k, v in kwargs.items()])",]
    lines += indent(types, join=False)
    refinenum = lambda x: (sum([int(ts.isrefinement(a[1])) for a in x[0][1:]]),
                           len(x[0]), x[1])
    mangitems = sorted(name_mangled.items(), key=refinenum)
    mtypeslines = []
    lines += indent("# vtable-like dispatch for exactly matching types", join=False)
    for key, mangled_name in mangitems:
        cargs = key[1:]
        arang = range(len(cargs))
        anames = [ca[0] for ca in cargs]
        pytypes = [ts.cython_pytype(ca[1]) for ca in cargs]
        mtypes = ", ".join(
            ["({0}, {1})".format(i, pyt) for i, pyt in zip(arang, pytypes)] + \
            ['("{0}", {1})'.format(n, pyt) for n, pyt in zip(anames, pytypes)])
        mtups = '(' + mtypes + ')' if 0 < len(mtypes) else mtypes
        mtypeslines.append(mangled_name + "_argtypes = frozenset(" + mtups + ")")
        cond = [arg_chk_str.format(mangled_name),]
        if hasrtn:
            rline = dispatch_str_ret.format(mangled_name)
        else:
            rline = [dispatch_str_no_ret.format(mangled_name), "return"]
        cond += indent(rline, join=False)
        lines += indent(cond, join=False)
    lines = sorted(mtypeslines) + [''] +  lines
    lines += indent("# duck-typed dispatch based on whatever works!", join=False)
    refineopp = lambda x: (-1*sum([int(ts.isrefinement(a[1])) for a in x[0][1:]]), len(x[0]), x[1])
    mangitems = sorted(name_mangled.items(), key=refineopp)
    for key, mangled_name in mangitems:
        lines += indent('try:', join=False)
        if hasrtn:
            rline = dispatch_str_ret.format(mangled_name)
        else:
            rline = [dispatch_str_no_ret.format(mangled_name), "return"]
        lines += indent(indent(rline, join=False), join=False)
        lines += indent(["except (RuntimeError, TypeError, NameError):",
                         indent("pass", join=False)[0],], join=False)
    errmsg = "raise RuntimeError('method {0}() could not be dispatched')".format(name)
    lines += indent(errmsg, join=False)
    lines += ['']
    return lines


def _class_heirarchy(cls, ch, classes):
    if not classes[cls]['parents']:
        return
    if 0 == len(ch) or ch[0] != cls:
        ch.insert(0, cls)
    for p in classes[cls]['parents'][::-1]:
        ch.insert(0, p)
        _class_heirarchy(p, ch, classes)

def _method_instance_names(desc, classes, key, rtn, ts):
    classnames = []
    _class_heirarchy(desc['name']['tarname'], classnames, classes)
    for classname in classnames:
        classrtn = classes.get(classname, {}).get('methods', {})\
                          .get(key, {}).get('return', NotImplemented)
        if rtn != classrtn:
            continue
        class_ctype = ts.cython_ctype(classname)
        inst_name = "(<{0} *> self._inst)".format(class_ctype)
        return inst_name, classname
    tarname = desc['name']['tarname']
    return "(<{0} *> self._inst)".format(ts.cython_ctype(tarname)), tarname


def _count0(x):
    c = {}
    for v in x:
        v0 = v[0]
        c[v0] = c.get(v0, 0) + 1
    return c

def _doc_add_sig(doc, name, args, defaults, ismethod=True):
    if doc.startswith(name):
        return doc
    sig = ['self'] if ismethod else []
    sig.append(_gen_argfill(args, defaults)[0])
    newdoc = "{0}({1})\n{2}".format(name, ", ".join(sig), doc)
    return newdoc


_pyx_class_template = \
'''{function_pointer_block}

cdef class {name}{parents}:
{class_docstring}

{cdefattrs}

    # constuctors
    def __cinit__(self, *args, **kwargs):
        self._inst = NULL
        self._free_inst = True

        # cached property defaults
{property_defaults}

{constructor_block}

    # attributes
{attrs_block}
    # methods
{methods_block}

    pass

{extra}
'''

def classpyx(desc, classes=None, ts=None, max_callbacks=8):
    """Generates a ``*.pyx`` Cython wrapper implementation for exposing a C/C++
    class based off of a dictionary description.  The environment is a
    dictionary of all class names known to their descriptions.

    Parameters
    ----------
    desc : dict
        Class description dictonary.
    classes : dict, optional
        Dictionary which maps all class names that are required to
        their own descriptions.  This is required for resolving class heirarchy
        dependencies.
    ts : TypeSystem, optional
        A type system instance.
    max_callbacks : int, optional
        The default maximum number of callbacks for function pointers.

    Returns
    -------
    pyx : str
        Cython ``*.pyx`` implementation file as in-memory string.

    """
    ts = ts or TypeSystem()
    if classes is None:
        classes = {desc['name']['tarname']: desc}
    src_lang = desc['name']['language']
    nodocmsg = "no docstring for {0}, please file a bug report!"
    pars = ', '.join([ts.cython_cytype(p) for p in desc['parents']])
    d = {'parents': '('+pars+')' if pars else '',
         'namespace': desc['namespace'],
         }
    srcname = desc['name']['srcname']
    name = desc['name']['tarname']
    d['name'] = ts.cython_classname(name)[1]
    class_doc = desc.get('docstrings', {}).get('class', nodocmsg.format(desc['name']))
    d['class_docstring'] = indent('\"\"\"{0}\"\"\"'.format(class_doc))

    class_ctype = ts.cython_ctype(name)
    inst_name = "(<{0} *> self._inst)".format(class_ctype)

    import_tups = set()
    cimport_tups = set()
    for parent in desc['parents']:
        ts.cython_import_tuples(parent, import_tups)
        ts.cython_cimport_tuples(parent, cimport_tups)

    cdefattrs = []
    mc = desc.get('extra', {}).get('max_callbacks', max_callbacks)

    alines = []
    pdlines = []
    fplines = []
    cached_names = []
    attritems = sorted(desc['attrs'].items())
    for aname, atype in attritems:
        if aname.startswith('_'):
            continue  # skip private
        adoc = desc.get('docstrings', {}).get('attrs', {})\
                                         .get(aname, nodocmsg.format(aname))
        if ts.isfunctionpointer(atype):
            alines += _gen_function_pointer_property(aname, atype, ts, adoc,
                        cached_names=cached_names, inst_name=inst_name,
                        classname=name, max_callbacks=mc)
            fplines += _gen_function_pointer_wrapper(aname, atype, ts,
                        max_callbacks=mc, classname=name)
            pdlines.append("self._{0}_vtab_i = {1}".format(aname, mc+1))
        else:
            alines += _gen_property(aname, atype, ts, adoc, cached_names=cached_names,
                                    inst_name=inst_name, classes=classes)
        ts.cython_import_tuples(atype, import_tups)
        ts.cython_cimport_tuples(atype, cimport_tups)
    if len(fplines) > 0:
        fplines.append("_MAX_CALLBACKS_{0} = {1}".format(name, mc))
    d['attrs_block'] = indent(alines)
    d['function_pointer_block'] = "\n".join(fplines)
    pdlines += ["{0} = None".format(n) for n in cached_names]
    d['property_defaults'] = indent(indent(pdlines, join=False))

    mlines = []
    clines = []
    methcounts = _count0(desc['methods'])
    currcounts = dict([(k, 0) for k in methcounts])
    mangled_mnames = {}
    mitems = list(desc['methods'].items())
    methitems = sorted(x for x in mitems if isinstance(x[0][0], basestring))
    methitems += sorted(x for x in mitems if not isinstance(x[0][0], basestring))
    for mkey, mval in methitems:
        mname, margs = mkey[0], mkey[1:]
        mrtn = mval['return']
        mdefs = mval['defaults']
        mbasename = mname if isinstance(mname, basestring) else mname[0]
        mcyname = ts.cython_funcname(mname)
        if mbasename.startswith('_'):
            continue  # skip private
        if any([a[1] is None or a[1][0] is None for a in margs]):
            continue
        if 1 < methcounts[mname]:
            mname_mangled = "_{0}_{1}_{2:0{3}}".format(d['name'], mcyname,
                    currcounts[mname], int(math.log(methcounts[mname], 10)+1)).lower()
        else:
            mname_mangled = mcyname
        currcounts[mname] += 1
        mangled_mnames[mkey] = mname_mangled
        for a in margs:
            ts.cython_import_tuples(a[1], import_tups)
            ts.cython_cimport_tuples(a[1], cimport_tups)
        minst_name, mcname = _method_instance_names(desc, classes, mkey, mrtn, ts)
        if mcname != d['name']:
            ts.cython_import_tuples(mcname, import_tups)
            ts.cython_cimport_tuples(mcname, cimport_tups)
        if mrtn is None:
            # this must be a constructor
            if mname not in (d['name'], '__init__',
              srcname if isinstance(srcname, basestring) else srcname[:-1]):
                continue  # skip destuctors
            if 1 == methcounts[mname]:
                mname_mangled = '__init__'
                mangled_mnames[mkey] = mname_mangled
            mdoc = desc.get('docstrings', {}).get('methods', {}).get(mname, '')
            mdoc = _doc_add_sig(mdoc, mcyname, margs, mdefs)
            construct = desc['construct']
            if construct in ('struct', 'union'):
                cimport_tups.add(('libc.stdlib', 'malloc'))
            clines += _gen_constructor(mcyname, mname_mangled, d['name'], margs,
                        mdefs, ts, doc=mdoc,
                        srcpxd_filename=desc['srcpxd_filename'],
                        inst_name=minst_name, construct=construct,
                        src_lang=src_lang)
            if 1 < methcounts[mname] and currcounts[mname] == methcounts[mname]:
                # write dispatcher
                nm = {}
                for k, v in mangled_mnames.items():
                    if isinstance(k[0], basestring) and k[0] == mbasename:
                        nm[k] = v
                    elif k[0] == srcname[:-1]:
                        nm[k] = v
                clines += _gen_dispatcher('__init__', nm, ts, doc=mdoc, hasrtn=False)
        else:
            # this is a normal method
            ts.cython_import_tuples(mrtn, import_tups)
            ts.cython_cimport_tuples(mrtn, cimport_tups)
            mdoc = desc.get('docstrings', {}).get('methods', {})\
                                             .get(mname, nodocmsg.format(mname))
            mdoc = _doc_add_sig(mdoc, mcyname, margs, mdefs)
            mlines += _gen_function(mcyname, mname_mangled, margs, mrtn, mdefs,
                                    ts, mdoc, inst_name=minst_name,
                                    is_method=True)
            if 1 < methcounts[mname] and currcounts[mname] == methcounts[mname]:
                # write dispatcher
                nm = dict([(k, v) for k, v in mangled_mnames.items() \
                           if k[0] == mbasename])
                mlines += _gen_dispatcher(mcyname, nm, ts, doc=mdoc)
    if 0 == len(desc['methods']) or 0 == len(clines):
        # provide a default constructor
        mdocs = desc.get('docstrings', {}).get('methods', {})
        mdoc = mdocs.get(desc['name']['tarname'], False) or mdocs.get('__init__', '')
        attrsargs = [(Arg.LIT, "None")] * len(attritems)
        mdoc = _doc_add_sig(mdoc, '__init__', attritems, attrsargs)
        clines += _gen_default_constructor(desc, attritems, ts, doc=mdoc)
        cimport_tups.add(('libc.stdlib', 'malloc'))
    if not desc['parents']:
        clines += ["def __dealloc__(self):"]
        clines += indent("if self._free_inst and self._inst is not NULL:", join=False)
        clines += indent(indent("free(self._inst)", join=False), join=False)
        cimport_tups.add(('libc.stdlib', 'free'))

    # Add dispatcher for templated methods
    template_meths = _template_method_names(desc['methods'])
    template_dispatcher = _gen_template_func_dispatcher(template_meths, ts)
    mlines.append(indent(template_dispatcher))

    d['methods_block'] = indent(mlines)
    d['constructor_block'] = indent(clines)

    d['extra'] = desc.get('extra', {}).get('pyx', '')
    d['cdefattrs'] = indent(cdefattrs)
    pyx = _pyx_class_template.format(**d)
    if 'pyx_filename' not in desc:
        desc['pyx_filename'] = '{0}.pyx'.format(d['name'].lower())
    return import_tups, cimport_tups, pyx

def varpyx(desc, ts=None):
    """Generates a ``*.pyx`` Cython wrapper implementation for exposing a C/C++
    variable based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        Variable description dictonary.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    pyx : str
        Cython ``*.pyx`` implementation as in-memory string.

    """
    ts = ts or TypeSystem()
    nodocmsg = "no docstring for {0}, please file a bug report!"
    inst_name = desc['extra']['srcpxd_filename'].rsplit('.', 1)[0]
    import_tups = set()
    cimport_tups = set(((inst_name,),))
    name = desc['name']
    t = ts.canon(desc['type'])

    vlines = []
    if ts.isenum(t):
        doc = '"' * 3 +  "{0} is enumeration {1} of {2}" + '"' * 3
        for ename, val in t[1][2][2]:
            vlines.append("{0} = {1}.{0}".format(ename, inst_name))
            vlines.append(doc.format(ename, val, name))
            vlines.append("")
    else:
        decl, body, rtn, iscached = ts.cython_c2py(name, t, view=False, cached=False,
                                                inst_name=inst_name)
        vlines.append(decl)
        vlines.append(body)
        vlines.append(name + " = " + rtn)
        docstring = desc.get('docstring', None)
        if docstring is None:
            docstring = '"' * 3 + nodocmsg.format(name) + '"' * 3
        vlines.append(docstring)

    vlines.append(desc.get('extra', {}).get('pyx', ''))
    pyx = '\n'.join(vlines)
    extra = desc['extra']
    if 'pyx_filename' not in extra:
        extra['pyx_filename'] = '{0}.pyx'.format(desc['name']['tarbase'])
    return import_tups, cimport_tups, pyx


def funcpyx(desc, ts=None):
    """Generates a ``*.pyx`` Cython wrapper implementation for exposing a C/C++
    function based off of a dictionary description.

    Parameters
    ----------
    desc : dict
        function description dictonary.
    ts : TypeSystem, optional
        A type system instance.

    Returns
    -------
    pyx : str
        Cython ``*.pyx`` implementation as in-memory string.

    """
    ts = ts or TypeSystem()
    nodocmsg = "no docstring for {0}, please file a bug report!"
    inst_name = desc['extra']['srcpxd_filename'].rsplit('.', 1)[0]

    import_tups = set()
    cimport_tups = set(((inst_name,),))

    # For renaming
    ftopname = desc['name']['tarname']
    fcytopname = ts.cython_funcname(ftopname)

    flines = []
    funccounts = _count0(desc['signatures'])
    currcounts = dict([(k, 0) for k in funccounts])
    mangled_fnames = {}
    funcitems = sorted(desc['signatures'].items())
    for fkey, fval in funcitems:
        fname, fargs = fkey[0], fkey[1:]
        frtn = fval['return']
        fdefs = fval['defaults']
        fbasename = fname if isinstance(fname, basestring) else fname[0]
        #fcppname = ts.cpp_funcname(fname)
        fcyname = ts.cython_funcname(fname)
        if fbasename.startswith('_'):
            continue  # skip private
        if any([a[1] is None or a[1][0] is None for a in fargs + (frtn,)]):
            continue
        if 1 < funccounts[fname]:
            fname_mangled = "_{0}_{1:0{2}}".format(fcytopname, currcounts[fname],
                                        int(math.log(funccounts[fname], 10)+1)).lower()
        else:
            fname_mangled = fcytopname
        currcounts[fname] += 1
        mangled_fnames[fkey] = fname_mangled
        for a in fargs:
            ts.cython_import_tuples(a[1], import_tups)
            ts.cython_cimport_tuples(a[1], cimport_tups)
        ts.cython_import_tuples(frtn, import_tups)
        ts.cython_cimport_tuples(frtn, cimport_tups)
        fdoc = desc.get('docstring', nodocmsg.format(fcyname))
        fdoc = _doc_add_sig(fdoc, fcyname, fargs, fdefs, ismethod=False)
        flines += _gen_function(fcyname, fname_mangled, fargs, frtn, fdefs, ts,
                                fdoc, inst_name=inst_name, is_method=False)
        if 1 < funccounts[fname] and currcounts[fname] == funccounts[fname]:
            # write dispatcher
            nm = dict([(k, v) for k, v in mangled_fnames.items() if k[0] == fname])
            flines += _gen_dispatcher(fcytopname, nm, ts, doc=fdoc, is_method=False)
    flines.append(desc.get('extra', {}).get('pyx', ''))
    pyx = '\n'.join(flines)
    extra = desc['extra']
    if 'pyx_filename' not in extra:
        extra['pyx_filename'] = '{0}.pyx'.format(extra['name']['tarbase'])
    return import_tups, cimport_tups, pyx

#
# Plugin
#

class XDressPlugin(Plugin):
    """The main cython generator plugin.
    """

    requires = ('xdress.autodescribe',)
    """This plugin requires autodescribe."""

    defaultrc = {'max_callbacks': 8}

    rcdocs = {
        "max_callbacks": "The maximum number of callbacks for function pointers",
        }

    def update_argparser(self, parser):
        parser.add_argument('--max-callbacks', type=int, dest="max_callbacks",
                    help=self.rcdocs["max_callbacks"])

    def setup(self, rc):
        if rc.max_callbacks < 1:
            raise ValueError("max_callbacks must be greater than or equal to 1")
        if cython_version is None:
            warnings.warn('cython does not seem to be installed', RuntimeWarning)
        elif cython_version_info[:2] <= (0, 17):
            warnings.warn('cython code generated by xdress requires cython v0.18+, '
                          'cython version {0} found'.format(cython_version),
                          RuntimeWarning)

    def execute(self, rc):
        print("cythongen: creating C/C++ API wrappers")
        env = rc.env
        classes = {}
        for modname, mod in env.items():
            for name, desc in mod.items():
                if isclassdesc(desc):
                    classes[name] = desc

        # generate all files
        cpppxds = gencpppxd(env, ts=rc.ts)
        pxds = genpxd(env, classes, ts=rc.ts, max_callbacks=rc.max_callbacks)
        pyxs = genpyx(env, classes, ts=rc.ts, max_callbacks=rc.max_callbacks)

        # write out all files
        for key, cpppxd in cpppxds.items():
            newoverwrite(cpppxd, os.path.join(rc.packagedir,
                         env[key]['srcpxd_filename']), rc.verbose)
        for key, pxd in pxds.items():
            newoverwrite(pxd, os.path.join(rc.packagedir,
                         env[key]['pxd_filename']), rc.verbose)
        for key, pyx in pyxs.items():
            newoverwrite(pyx, os.path.join(rc.packagedir,
                         env[key]['pyx_filename']), rc.verbose)


#
# Misc Helpers Below
#

def _format_ns(desc):
    ns = desc.get('namespace', None)
    if ns is None:
        return ""
    elif len(ns) == 0:
        return ""
    else:
        return 'namespace "{0}"'.format(ns)

def _format_alias(desc, ts):
    ns = desc.get('namespace', None)
    cpp_name = ts.cpp_type(desc['name']['tarname'])
    if ns is None or len(ns) == 0:
        return '"{0}"'.format(cpp_name)
    else:
        return '"{0}::{1}"'.format(ns, cpp_name)

def _mangle_function_pointer_name(name, classname):
    pyref = "_xdress_{0}_{1}_proxy".format(classname, name)
    cref = pyref + "_func"
    return pyref, cref

def _isclassptr(t, classes):
    return (not isinstance(t, basestring) and t[1] == '*' and
            isinstance(t[0], basestring) and t[0] in classes)

def _isclassdblptr(t, classes):
    if 2 != len(t):
        return False
    return _isclassptr(t[0], classes) and t[1] == '*'

_exc_c_base = frozenset(['int16', 'int32', 'int64', 'int128',
                         'float32', 'float64', 'float128'])

_exc_ptr_matcher = TypeMatcher((MatchAny, '*'))

def _exception_str(exceptions, lang, rtntype, ts):
    if not exceptions:
        return ""
    if isinstance(exceptions, basestring):
        return "except " + exceptions

    if lang == 'c':
        if rtntype is None:
            return "except -1"  # helpful when we accidentally mis-guessed C for C++
        rtntype = ts.canon(rtntype)
        return ""
        # The following does not work in general since valid vs invalid returns
        # cannot be known.
        #if rtntype in _exc_c_base:
        #    return "except -1"
        #elif ts.isenum(rtntype):
        #    return "except -1"
        #elif _exc_ptr_matcher.matches(rtntype):
        #    return "except -1"
        #else:
        #    return ""
    elif lang == 'c++':
        return "except +"
    else:
        return ""

def _template_method_names(methods):
    methnames = set()
    for sig, val in methods.items():
        name = sig[0]
        if isinstance(name, basestring):
            continue
        elif val['return'] is None:  # ignore constructor / destructors
            continue
        methnames.add(name)
    return methnames

def _template_funcnames_in_mod(mod):
    funcnames = set()
    for name, desc in mod.items():
        if isinstance(name, basestring) or not isfuncdesc(desc):
            continue
        funcnames.add(name)
    return funcnames

def _classnames_in_mod(mod, ts):
    classnames = set()
    for name, desc in mod.items():
        if not isclassdesc(desc):
            continue
        classnames.add(name)
        classnames.add(ts.basename(name))
        classnames.add(desc['type'])
    return classnames

def _template_classnames_in_mod(mod):
    classnames = set()
    for name, desc in mod.items():
        if isinstance(name, basestring) or not isclassdesc(desc):
            continue
        classnames.add(name)
    return classnames

Generates a Cython wrappers from description dictionaries. This module relies heavily on the type system to convert between C/C++, Cython, and Python types in a seamless way.  While this module does not explicitly rely on the auto-describer, it sure helps!  The functions in this module are conceptually easy to understand -- given class descriptions they generate strings of Cython code -- their implementations do a lot of heavy lifting.  This module is available as an xdress plugin by the name ``xdress.cythongen``. Note that while the module does not rely on the autodescriber, the plugin does.  :author: <NAME> <<EMAIL>>  Cython Generation API ===================== ################################################ #                 WARNING!                     # # This file has been auto-generated by xdress. # # Do not modify!!!                             # #                                              # #                                              # #                    Come on, guys. I mean it! # ################################################ Generates all cpp_*.pxd Cython header files for an environment of modules.      Parameters     ----------     env : dict         Environment dictonary mapping target module names to module description         dictionaries.     exceptions : bool or str, optional         Cython exception annotation.  Set to True to automatically detect exception         types, False for when exceptions should not be included, and a str (such as         '+' or '-1') to apply to everywhere.     ts : TypeSystem, optional         A type system instance.      Returns     -------     cpppxds : dict         Maps environment target names to Cython cpp_*.pxd header files strings. Sorts the variable names in a cpp_*.pxd module so that C/C++     declarations happen in the proper order. Generates a cpp_*.pxd Cython header file for exposing a C/C++ module to     other Cython wrappers based off of a dictionary description of the module.      Parameters     ----------     mod : dict         Module description dictonary.     exceptions : bool or str, optional         Cython exception annotation.  Set to True to automatically detect exception         types, False for when exceptions should not be included, and a str (such as         '+' or '-1') to apply to everywhere.     ts : TypeSystem, optional         A type system instance.      Returns     -------     cpppxd : str         Cython cpp_*.pxd header file as in-memory string. # function signatures cdef extern from "{header_filename}" {namespace}:  {variables_block}  {extra} Generates a cpp_*.pxd Cython header snippet for exposing a C/C++ variable     to other Cython wrappers based off of a dictionary description.      Parameters     ----------     desc : dict         Function description dictonary.     exceptions : bool or str, optional         Cython exception annotation.  Set to True to automatically detect exception         types, False for when exceptions should not be included, and a str (such as         '+' or '-1') to apply to everywhere.     ts : TypeSystem, optional         A type system instance.      Returns     -------     cimport_tups : set of tuples         Set of Cython cimport tuples for cpp_*.pxd header file.     cpppxd : str         Cython cpp_*.pxd header file as in-memory string. # function signatures cdef extern from "{header_filename}" {namespace}:  {functions_block}  {extra} Generates a cpp_*.pxd Cython header snippet for exposing a C/C++ function     to other Cython wrappers based off of a dictionary description.      Parameters     ----------     desc : dict         Function description dictonary.     exceptions : bool or str, optional         Cython exception annotation.  Set to True to automatically detect exception         types, False for when exceptions should not be included, and a str (such as         '+' or '-1') to apply to everywhere.     ts : TypeSystem, optional         A type system instance.      Returns     -------     cimport_tups : set of tuples         Set of Cython cimport tuples for cpp_*.pxd header file.     cpppxd : str         Cython cpp_*.pxd header file as in-memory string. # private cdef extern from "{header_filename}" {namespace}:      cdef {construct_kind} {name}{alias}{parents}:         # constructors {constructors_block}          # attributes {attrs_block}          # methods {methods_block}         pass  {extra} Generates a cpp_*.pxd Cython header snippet for exposing a C/C++ class or     struct to other Cython wrappers based off of a dictionary description of the     class or struct.      Parameters     ----------     desc : dict         Class description dictonary.     exceptions : bool or str, optional         Cython exception annotation.  Set to True to automatically detect exception         types, False for when exceptions should not be included, and a str (such as         '+' or '-1') to apply to everywhere.     ts : TypeSystem, optional         A type system instance.      Returns     -------     cimport_tups : set of tuples         Set of Cython cimport tuples for cpp_*.pxd header file.     cpppxd : str         Cython cpp_*.pxd header file as in-memory string. # C # private or destructor # this must be a constructor # this is a normal method Generates all pxd Cython header files for an environment of modules.      Parameters     ----------     env : dict         Environment dictonary mapping target module names to module description         dictionaries.     classes : sequence, optional         Listing of all class names that are handled by cythongen.  This may be         the same dictionary as in genpyx()     ts : TypeSystem, optional         A type system instance.     max_callbacks : int, optional         The default maximum number of callbacks for function pointers.      Returns     -------     pxds : str         Maps environment target names to Cython pxd header files strings. Sorts the names in a module to make sure that pxd declarations happen in the     proper order. Generates a pxd Cython header file for exposing C/C++ data to     other Cython wrappers based off of a dictionary description.      Parameters     ----------     mod : dict         Module description dictonary.     classes : sequence, optional         Listing of all class names that are handled by cythongen.  This may be         the same dictionary as in modpyx().     ts : TypeSystem, optional         A type system instance.     max_callbacks : int, optional         The default maximum number of callbacks for function pointers.      Returns     -------     pxd : str         Cython .pxd header file as in-memory string. # no need to wrap functions again {function_pointer_block}  cdef class {name}{parents}: {body}     pass  {extra} Generates a ``*pxd`` Cython header snippet for exposing a C/C++ class to     other Cython wrappers based off of a dictionary description.      Parameters     ----------     desc : dict         Class description dictonary.     classes : sequence, optional         Listing of all class names that are handled by cythongen.  This may be         the same dictionary as in modpyx().     ts : TypeSystem, optional         A type system instance.     max_callbacks : int, optional         The default maximum number of callbacks for function pointers.      Returns     -------     cimport_tups : set of tuples         Set of Cython cimport tuples for .pxd header file.     pxd : str         Cython ``*.pxd`` header snippet for class. # skip private Generates all pyx Cython implementation files for an environment of modules.      Parameters     ----------     env : dict         Environment dictonary mapping target module names to module description         dictionaries.     classes : dict, optional         Dictionary which maps all class names that are required to         their own descriptions.  This is required for resolving class heirarchy         dependencies. If None, this will be computed here.     ts : TypeSystem, optional         A type system instance.     max_callbacks : int, optional         The default maximum number of callbacks for function pointers.      Returns     -------     pyxs : str         Maps environment target names to Cython pxd header files strings. # get flat namespace of class descriptions # gen files """{docstring} """ {cimports}  {imports}  {attrs_block}  {extra} Generates a pyx Cython implementation file for exposing C/C++ data to     other Cython wrappers based off of a dictionary description.      Parameters     ----------     mod : dict         Module description dictonary.     classes : dict, optional         Dictionary which maps all class names that are required to         their own descriptions.  This is required for resolving class heirarchy         dependencies.     ts : TypeSystem, optional         A type system instance.     max_callbacks : int, optional         The default maximum number of callbacks for function pointers.      Returns     -------     pyx : str         Cython pyx header file as in-memory string. # Add dispatcher for template functions # Add dispatcher for template classes #cimport_tups.discard((mod["name"],))  # remain commented for decls Generates the fill values for an argument of a type into a template type t. Generates a dictionary-based dispacher for template functions. Generates a dictionary-based dispacher for template classes. This generates a Cython property getter for a variable of a given     name and type. This generates a Cython property setter for a variable of a given     name and type. This generates a Cython property for a variable of a given name and type. This generates a Cython property for a function pointer variable. # get section # set section #lines += setlines[1:] This generates a Cython wrapper for a function pointer variable. # Function pointer helpers for {1}.{0}\n#".format(name, classname), Generate argument list for a function, and return (argfill, names).     If any argument names or empty, the corresponding entry in names will     be '_n' for some integer n. # Empty name, generate a fresh dummy symbol # Only call the default constructor when it makes sense. # Only call the default constructor when it makes sense. # string to format for arg checking # string to format for dispatching and returning # Make self a method argument or not {function_pointer_block}  cdef class {name}{parents}: {class_docstring}  {cdefattrs}      # constuctors     def __cinit__(self, *args, **kwargs):         self._inst = NULL         self._free_inst = True          # cached property defaults {property_defaults}  {constructor_block}      # attributes {attrs_block}     # methods {methods_block}      pass  {extra} Generates a ``*.pyx`` Cython wrapper implementation for exposing a C/C++     class based off of a dictionary description.  The environment is a     dictionary of all class names known to their descriptions.      Parameters     ----------     desc : dict         Class description dictonary.     classes : dict, optional         Dictionary which maps all class names that are required to         their own descriptions.  This is required for resolving class heirarchy         dependencies.     ts : TypeSystem, optional         A type system instance.     max_callbacks : int, optional         The default maximum number of callbacks for function pointers.      Returns     -------     pyx : str         Cython ``*.pyx`` implementation file as in-memory string. # skip private # skip private # this must be a constructor # skip destuctors # write dispatcher # this is a normal method # write dispatcher # provide a default constructor # Add dispatcher for templated methods Generates a ``*.pyx`` Cython wrapper implementation for exposing a C/C++     variable based off of a dictionary description.      Parameters     ----------     desc : dict         Variable description dictonary.     ts : TypeSystem, optional         A type system instance.      Returns     -------     pyx : str         Cython ``*.pyx`` implementation as in-memory string. Generates a ``*.pyx`` Cython wrapper implementation for exposing a C/C++     function based off of a dictionary description.      Parameters     ----------     desc : dict         function description dictonary.     ts : TypeSystem, optional         A type system instance.      Returns     -------     pyx : str         Cython ``*.pyx`` implementation as in-memory string. # For renaming #fcppname = ts.cpp_funcname(fname) # skip private # write dispatcher # # Plugin # The main cython generator plugin. This plugin requires autodescribe. # generate all files # write out all files # # Misc Helpers Below # # helpful when we accidentally mis-guessed C for C++ # The following does not work in general since valid vs invalid returns # cannot be known. #if rtntype in _exc_c_base: #    return "except -1" #elif ts.isenum(rtntype): #    return "except -1" #elif _exc_ptr_matcher.matches(rtntype): #    return "except -1" #else: #    return "" # ignore constructor / destructors