#!/usr/bin/env python

from __future__ import print_function
range = xrange

import clang.cindex
import ctypes
import sys

import contextlib
import time
@contextlib.contextmanager
def timing(name=''):
    """Context manager that times the execution of a block"""
    before = time.time()
    yield
    after = time.time()
    indicator = '>>> Time{}:'.format(name and '(' + name + ')')
    print(indicator, 1000 * (after - before), "ms")


CursorKind = clang.cindex.CursorKind
TypeKind = clang.cindex.TypeKind

# It blows my mind that CursorKind has no easy way to introspect this. Hack
# around it.
CURSOR_KINDS = {}
for kind in CursorKind.get_all_kinds():
    CURSOR_KINDS[kind.name] = kind

TYPE_KINDS = {}
for k, v in TypeKind.__dict__.items():
    if isinstance(v, TypeKind):
        TYPE_KINDS[k] = v

PRIMITIVE_CTYPE_EQUIVALENTS = dict(
    BOOL       = ctypes.c_bool,
    CHAR_U     = ctypes.c_ubyte,
    UCHAR      = ctypes.c_ubyte,
    USHORT     = ctypes.c_ushort,
    UINT       = ctypes.c_uint,
    ULONG      = ctypes.c_ulong,
    ULONGLONG  = ctypes.c_ulonglong,
    CHAR_S     = ctypes.c_char,
    SCHAR      = ctypes.c_byte,
    WCHAR      = ctypes.c_wchar,
    SHORT      = ctypes.c_short,
    INT        = ctypes.c_int,
    LONG       = ctypes.c_long,
    LONGLONG   = ctypes.c_longlong,
    FLOAT      = ctypes.c_float,
    DOUBLE     = ctypes.c_double,
    LONGDOUBLE = ctypes.c_longdouble
)

# keyed on the decl, values are `ctypes` types
CACHED_TYPES = {}

def _forward_declare(name):
    """Create a class inheriting from ctypes.Structure with the given name."""
    # This create a class (yes, like, a Python class; for realsies). It
    # does the equivalent of:
    #
    #   class <name>(ctypes.Structure):
    #       pass
    #
    # If you don't know what a metaclass is then don't bother trying to
    # understand what is going on here. If you want to learn, start by
    # looking at the three-argument version of `type` (which does something
    # completely different from the one-argument version that,
    # incidentally, I use here).
    #
    # Thankfully, ctypes.Structure already has the capability to be forward
    # declared, so we just need to fabricate the class.
    meta = type(ctypes.Structure)
    return meta(name, (ctypes.Structure,), {})


def clangty2ctypes(clangty):
    """Convert a clang type to a `ctypes` type.

    It is not wise to try to make this routine handle the full generality
    of C++ types. If you want to do that, then you should first start by
    exposing to Python the functionality contained in Clang's
    ASTRecordLayout class.
    """
    def _recurse(ty):
        kind = ty.kind
        if kind.name in PRIMITIVE_CTYPE_EQUIVALENTS:
            return PRIMITIVE_CTYPE_EQUIVALENTS[kind.name]
        if kind is TypeKind.CONSTANTARRAY:
            return _recurse(ty.element_type) * ty.element_count
        if kind is TypeKind.RECORD:
            return _build_record(ty)
        if kind is TypeKind.POINTER:
            return ctypes.POINTER(_recurse(ty.get_pointee()))

        # prompty alert us to the unhandled case
        raise Exception(ty.kind.name)

    def _build_record(ty):
        decl = ty.get_declaration()
        assert (decl.kind is CursorKind.STRUCT_DECL or
                decl.kind is CursorKind.CLASS_DECL), "unknown RECORD decl"
        key = decl.get_usr()
        if key in CACHED_TYPES and not hasattr(CACHED_TYPES[key], '_fields_'):
            return CACHED_TYPES[key]
        CACHED_TYPES[key] = _forward_declare(decl.spelling)

        field_list = [] # suitable for _fields_ of ctypes.Structure
        for child in decl.get_children():
            if child.kind is not CursorKind.FIELD_DECL:
                continue
            field_name = child.spelling
            ctypes_ty = clangty2ctypes(child.type) # the magic of recursion
            field_list.append((field_name, ctypes_ty))

        CACHED_TYPES[key]._fields_ = field_list

        return CACHED_TYPES[key]

    return _recurse(clangty.get_canonical())



class CursorVisitor(object):
    # this insanely dynamic brilliance inspired by the Python `ast` module
    def visit(self, node):
        """Visitation dispatcher"""
        method = 'visit_' + node.kind.name
        visitor = getattr(self, method, self.generic_visit)
        return visitor(node)
    def generic_visit(self, node):
        """Fallback visitation function if visit_FOO is not defined"""
        for child in node.get_children():
            self.visit(child)

class PrintRecords(CursorVisitor):
    def visit_STRUCT_DECL(self, cursor):
        if not cursor.is_definition():
            return
        print("visiting:", cursor.displayname, cursor.extent)
        for child in cursor.get_children():
            print("child: ", child.kind, child.displayname)


# this (left, right) business is a brilliant way of printing types that I
# learned from Clang
def render_ctypesty(ty, struct_levels=1):
    def _indent(s):
        return s.replace('\n', '  \n')
    tyname = type(ty).__name__

    if tyname == 'PyCArrayType':
        left, right = render_ctypesty(ty._type_, struct_levels)
        return (_indent(left), '{}[{}]'.format(right, ty._length_))

    if tyname == 'PyCStructType':
        if struct_levels == 0:
            return ('struct '+ ty.__name__, '')
        l = []
        for field, ctype in ty._fields_:
            left, right = render_ctypesty(ctype, struct_levels-1)
            l.append('  {left} {fieldname}{right};'
                     .format(fieldname=field, left=_indent(left), right=right))
        return ('struct {tagname} {{\n'
                    '{fields}\n'
                '}}'.format(tagname=ty.__name__, fields='\n'.join(l)), '')

    if tyname == 'PyCPointerType':
        left, right = render_ctypesty(ty._type_, struct_levels)
        return (left + ' *', right)

    if tyname == 'PyCSimpleType':
        s = ty.__name__
        if s.startswith('c_'): # tidy up a bit
            s = s[2:]
        return (s, '')
    raise Exception(ty.__name__)


MAIN_FILE = sys.argv[1]

index = clang.cindex.Index.create()
tu = index.parse(MAIN_FILE, args='-x c'.split())
print('Translation unit:', tu.spelling)

for diag in tu.diagnostics:
    print(repr(diag))


structs = {}

for cursor in tu.cursor.get_children():
    if cursor.kind != CursorKind.STRUCT_DECL:
        continue
    if not cursor.is_definition():
        continue
    if cursor.loc.filename != MAIN_FILE:
        continue
    """
    print("visiting:", cursor.displayname, cursor.loc.line, cursor.type)
    for child in cursor.get_children():
        print("child: ", child.kind, child.displayname,
              child.type.kind.name,
              child.type.get_canonical().kind.name,
              clangty2ctypes(child.type))
    """

    ty = clangty2ctypes(cursor.type)
    structs[cursor.spelling] = ty
    left, right = render_ctypesty(ty)
    print(left, right)

import pprint
pprint.pprint(structs)
