#! /usr/bin/env python
# -*- coding: utf-8 -*-
# ______________________________________________________________________
'''scrape_multiarray_api

Utilities for reading the __multiarray_api.h file, and scraping three
things: symbolic names of API members, array indices for those
members, and LLVM types for those members.
'''
from __future__ import print_function, division, absolute_import
# ______________________________________________________________________

import sys

import pprint

# ______________________________________________________________________

TEMPLATE_STR = '''"""Automatically generated code.

Automatically generated by .../numba/scrape_multiarray_api.py

Edit at your own risk!
"""

import llvm.core as lc
from .llvm_types import _int1, _int8, _int32, _int64, _intp, _numpy_struct, \\
     _numpy_array

API_INDEX_MAP = %(indexmap)s

API_TYPE_MAP = %(tymap)s

# End of automatically generated code.
'''

# ______________________________________________________________________

def joinlines (source_lines):
    '''Remove backslashes appearing next to line breaks in a string
    and strip it after doing so.'''
    return ''.join((ln[:-1] if ln[-1] == '\\' else ln
                    for ln in source_lines.splitlines())).strip()

# ______________________________________________________________________

TY_MAP = {
    'char' : '_int8',
    'int' : '_int32', # This seems to hold true, even on 64-bit systems.
    'unsigned char' : '_int8', # XXX
    'unsigned int' : 'u_int32', # XXX
    'void' : 'lc.Type.void()',
    'npy_bool' : '_int8',
    'npy_intp' : '_intp',
    'npy_uint32' : 'u_int32', # XXX/Note: Loses unsigned info in LLVM type.
    'PyArrayObject' : '_numpy_struct',
    'double' : 'lc.Type.double()',
    'size_t' : 'u_intp', # XXX Loses unsigneded-ness
    'npy_int64' : '_int64',
    'npy_datetime' : '_int64', # npy_common.h
    'npy_timedelta' : '_int64', # npy_common.h
}

# ______________________________________________________________________

def map_type (ty_str):
    npointer = ty_str.count('*')
    if npointer == 0:
        base_ty = ty_str
    else:
        base_ty = ty_str[:-npointer].strip()
    if base_ty == 'void' and npointer > 0:
        base_ty = '_int8'
    elif base_ty not in TY_MAP:
        if npointer > 0:
            base_ty = '_int8' # Basically cast into void *
        else:
            base_ty = '_int32' # Or an int.
    else:
        base_ty = TY_MAP[base_ty]
        if base_ty == '_numpy_struct' and npointer > 0:
            base_ty = '_numpy_array'
            npointer -= 1
    return ''.join((npointer * 'lc.Type.pointer(', base_ty, ')' * npointer))

# ______________________________________________________________________

def c_ty_str_to_llvm (c_ty_str):
    ty_str_fn_split = [substr.strip() for substr in c_ty_str.split('(*)')]
    ret_val = map_type(ty_str_fn_split[0])
    if len(ty_str_fn_split) > 1:
        arg_ty_strs = ty_str_fn_split[1][1:-1].split(', ')
        if len(arg_ty_strs) == 1 and arg_ty_strs[0].strip() == 'void':
            arg_ty_strs = []
        ret_val = ('lc.Type.function(%s, [%s])' %
                   (ret_val, ', '.join((map_type(arg_ty_str.strip())
                                        for arg_ty_str in arg_ty_strs))))
    return ret_val

# ______________________________________________________________________

def process_type (ty_str):
    if ty_str.startswith('(*'):
        ty_str = ty_str[3:-1]
    else:
        assert ty_str[0] == '('
        ty_str = ty_str[2:-1]
    return ty_str

# ______________________________________________________________________

def process_definition (source_defn):
    arr_str = 'PyArray_API['
    arr_str_idx = source_defn.index(arr_str)
    arr_str_end_idx = source_defn.index('])', arr_str_idx)
    return (int(source_defn[arr_str_idx + len(arr_str):arr_str_end_idx]),
            process_type(joinlines(source_defn[:arr_str_idx].strip())))

# ______________________________________________________________________

def process_source (source_file_path):
    ret_val = None
    with open(source_file_path) as source_file:
        source_text = source_file.read()
    split_by_pp_define0 = source_text.split('#define ')
    split_by_pp_define1 = (substr0.strip().split(None, 1)
                           for substr0 in split_by_pp_define0)
    return dict(((sym_name, process_definition(sym_defn))
                 for sym_name, sym_defn in split_by_pp_define1
                 if (sym_name != 'PyArray_API' and
                     sym_defn.find('])') != -1)))

# ______________________________________________________________________

def gen_python (processed_source, template_str = None):
    if template_str is None:
        template_str = TEMPLATE_STR
    index_map = {}
    ty_map_strs = ['{']
    for symbol, (index, c_ty_str) in processed_source.items():
        index_map[symbol] = index
        ty_map_strs.append(' %r : %s,' % (symbol, c_ty_str_to_llvm(c_ty_str)))
    ty_map_strs.append('}')
    return template_str % {'indexmap' : pprint.pformat(index_map),
                           'tymap' : '\n'.join(ty_map_strs)}

# ______________________________________________________________________

def get_include ():
    import os, numpy
    return os.path.join(numpy.get_include(), 'numpy', '__multiarray_api.h')

# ______________________________________________________________________

def main (*args, **kws):
    '''Initial prototype for automatically generating multiarray C API
    call information for llvm-py.  Not actually used to generate any
    Numba modules (dynamically handled by multiarray_api).'''
    if len(args) == 0:
        args = (get_include(),)
    for arg in args:
        print((gen_python(process_source(arg))))

# ______________________________________________________________________

if __name__ == "__main__":
    import sys
    main(*sys.argv[1:])

# ______________________________________________________________________
# End of scrape_multiarray_api.py
