# -*- coding: utf-8 -*-
"""
Generate generated_conversions.c

Utilities adjusted from Cython/Compiler/PyrexTypes.pyx
"""
from __future__ import print_function, division, absolute_import

import os

func_name = "__Numba_PyInt_As%(SignWord)s%(TypeName)s"
header = "static %%(type)s %(FuncName)s(PyObject* x)" % { 'FuncName' : func_name}

c_int_from_py_function = """
%(Header)s {
    const %(type)s neg_one = (%(type)s)-1, const_zero = 0;
    const int is_unsigned = neg_one > const_zero;
    if (sizeof(%(type)s) < sizeof(long)) {
        long val = __Numba_PyInt_AsSignedLong(x);
        if (unlikely(val != (long)(%(type)s)val)) {
            if (!unlikely(val == -1 && PyErr_Occurred())) {
                PyErr_SetString(PyExc_OverflowError,
                    (is_unsigned && unlikely(val < 0)) ?
                    "can't convert negative value to %(type)s" :
                    "value too large to convert to %(type)s");
            }
            return (%(type)s)-1;
        }
        return (%(type)s)val;
    }
    return (%(type)s)__Numba_PyInt_As%(SignWord)sLong(x);
}
"""

c_long_from_py_function = """
%(Header)s {
    const %(type)s neg_one = (%(type)s)-1, const_zero = 0;
    const int is_unsigned = neg_one > const_zero;
#if PY_VERSION_HEX < 0x03000000
    if (likely(PyInt_Check(x))) {
        long val = PyInt_AS_LONG(x);
        if (is_unsigned && unlikely(val < 0)) {
            PyErr_SetString(PyExc_OverflowError,
                            "can't convert negative value to %(type)s");
            return (%(type)s)-1;
        }
        return (%(type)s)val;
    } else
#endif
    if (likely(PyLong_Check(x))) {
        if (is_unsigned) {
            if (unlikely(Py_SIZE(x) < 0)) {
                PyErr_SetString(PyExc_OverflowError,
                                "can't convert negative value to %(type)s");
                return (%(type)s)-1;
            }
            return (%(type)s)PyLong_AsUnsigned%(TypeName)s(x);
        } else {
            return (%(type)s)PyLong_As%(TypeName)s(x);
        }
    } else {
        %(type)s val;
        PyObject *tmp = __Numba_PyNumber_Int(x);
        if (!tmp) return (%(type)s)-1;
        val = __Numba_PyInt_As%(SignWord)s%(TypeName)s(tmp);
        Py_DECREF(tmp);
        return val;
    }
}
"""

def rank(types):
    types = [type for name, type in types]
    return dict(zip(types, range(len(exact_types))))

# Builtin C types that we know how to convert to/from objects
exact_types = (
    ("Char",     "char"),
    ("Short",    "short"),
    ("Int",      "int"),
    ("Long",     "long"),
    ("LongLong", "PY_LONG_LONG"),
)
rank_exact = rank(exact_types)

# Types for which we don't know the mapping to exact_types
inexact_types = (
    ("Py_ssize_t", "Py_ssize_t"),
    ("size_t",     "size_t"),
    ("npy_intp",   "npy_intp"),
)
rank_inexact = rank(inexact_types)

signednesses = (
    "signed",
    "unsigned",
)

def write_utility(exact_type, exact_type_name, out_c, out_h, signedness):
    # Select utility template
    if rank_exact[exact_type] < rank_exact["long"]:
        utility = c_int_from_py_function
    else:
        utility = c_long_from_py_function

    # Build argument dict
    fmtargs = { 'TypeName' : exact_type_name,
                'SignWord' : signedness.title(),
                'type'     : signedness + " " + exact_type }
    fmtargs.update(FuncName=func_name % fmtargs,
                   Header=header % fmtargs)

    # Write results
    conversion = utility % fmtargs
    out_c.write(conversion)
    out_h.write(header % fmtargs + ';\n')

    # print_export(fmtargs)
    # print_utility_load(fmtargs, signedness)


def generate_conversions(out_c, out_h):
    "Generate numba/external/utilities/generated_conversions.c"
    out_c.write("/* This file is generated by %s, do not edit */\n" %
                                           os.path.abspath(__file__))
    out_c.write('#include "%s"\n\n' % out_h.name)

    for exact_type_name, exact_type in exact_types:
        for signedness in signednesses:
            write_utility(exact_type, exact_type_name, out_c, out_h, signedness)

    # write_utility("char", "Char", out_c, out_h, "signed")

    print("Wrote %s and %s" % (out_c.name, out_h.name))


def print_export(fmtargs):
    "Code to put in type_conversion.c"
    print("EXPORT_FUNCTION(%(FuncName)s, module, error)" % fmtargs)

def print_utility_load(fmtargs, signedness):
    "Code to put in numba.external.utility"
    typename = fmtargs['TypeName'].lower()
    if signedness == "unsigned":
        typename = "u" + typename

    print('%-10s : load("%s", %s(object_)),' % (typename, fmtargs['FuncName'],
                                                typename))


def open_files():
    numba_root = os.path.dirname(os.path.abspath(__file__))
    root = os.path.join(numba_root, "numba", "external", "utilities")

    out_c = open(os.path.join(root, "generated_conversions.c"), "w")
    out_h = open(os.path.join(root, "generated_conversions.h"), "w")
    return out_c, out_h

def run():
    generate_conversions(*open_files())

if __name__ == "__main__":
    run()
