import os
from datetime import datetime

import parse

def arg_type(a):
    return {'libfive_tree': "libfive_tree",
            'tfloat': "tfloat",
            'tvec2':  "tvec2",
            'tvec3':  "tvec3",
            'float': 'ctypes.c_float',
            'const char*': "ctypes.c_char_p",
            'int':   'ctypes.c_int'}[a.type]

def arg_name(arg):
    if arg.default:
        if arg.type == 'tfloat':
            d = str(arg.default)
        elif arg.type == 'tvec2':
            d = "({0}, {0})".format(arg.default)
        elif arg.type == 'tvec3':
            d = "({0}, {0}, {0})".format(arg.default)
        else:
            raise RuntimeError("Unknown default: {}".format(a))
        return "{}={}".format(arg.name, d)
    else:
        return arg.name

def arg_wrap(a):
    if a.type in ['libfive_tree', 'tfloat']:
        return 'Shape.wrap({})'.format(a.name)
    elif a.type in ['float', 'int']:
        return a.name
    elif a.type == 'const char*':
        return "{}.encode('utf-8')".format(a.name)
    elif a.type == 'tvec2':
        return "list([Shape.wrap(i) for i in {}])".format(a.name)
    elif a.type == 'tvec3':
        return "list([Shape.wrap(i) for i in {}])".format(a.name)
    else:
        raise RuntimeError("Unknown type %s" % a.type)

def arg_call(a, i):
    if a.type in ['libfive_tree', 'tfloat']:
        return 'args[{}].ptr'.format(i)
    elif a.type in ['float', 'int', 'const char*']:
        return 'args[{}]'.format(i)
    elif a.type == 'tvec2':
        return "tvec2(*[a.ptr for a in args[{}]])".format(i)
    elif a.type == 'tvec3':
        return "tvec3(*[a.ptr for a in args[{}]])".format(i)
    else:
        raise RuntimeError("Unknown type %s" % a.type)

def format_module(lib, m):
    out = '''"""
Python bindings to the libfive CAD kernel

DO NOT EDIT BY HAND!
This file is automatically generated from libfive/stdlib/stdlib.h

It was last generated on {} by user {}

This is libfive.stdlib.{}
"""

from libfive.ffi import libfive_tree, tfloat, tvec2, tvec3, stdlib
from libfive.shape import Shape

import ctypes

'''.format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), os.getlogin(), m)

    for f in lib[m].shapes:
        arg_types = ", ".join(map(arg_type, f.args))
        arg_names = ", ".join(map(arg_name, f.args))
        arg_wraps = ", ".join(map(arg_wrap, f.args))
        arg_calls = ",\n        ".join([arg_call(a, i) for (i, a) in enumerate(f.args)])
        out += '''stdlib.{raw_name}.argtypes = [{arg_types}]
stdlib.{raw_name}.restype = libfive_tree
def {name}({arg_names}):
    """ {doc}
    """
    args = [{arg_wraps}]
    return Shape(stdlib.{raw_name}(
        {arg_calls}))

'''.format(raw_name=f.raw_name or f.name,
       name=f.name,
       doc=f.docstring.replace('\n', '\n        '),
       arg_wraps=arg_wraps,
       arg_types=arg_types,
       arg_names=arg_names,
       arg_calls=arg_calls)

    for a in lib[m].aliases:
        out += '''{} = {}
'''.format(a.name, a.target)
    return out[:-1]

################################################################################

append = {'csg': '''
################################################################################
# Hand-written functions which allow for arbitrary numbers of arguments
import functools

_prev_union = union
def union(a, *args):
    return functools.reduce(_prev_union, args, a)
union.__doc__ = _prev_union.__doc__

_prev_intersection = intersection
def intersection(a, *args):
    return functools.reduce(_prev_intersection, args, a)
intersection.__doc__ = _prev_intersection.__doc__

def difference(a, b, *rest):
    """ Subtracts any number of shapes from the first argument
    """
    return intersection(a, inverse(union(b, *rest)))
''',

 'transforms': '''
################################################################################
# Hand-written override to let move work in 2D
_move_prev = move
def move(shape, v):
    if len(v) == 2:
        v = [v[0], v[1], 0]
    return _move_prev(shape, v)
move.__doc__ = _move_prev.__doc__
''',
}

################################################################################


stdlib = parse.parse_stdlib()
for m in ['csg', 'shapes', 'generators', 'transforms', 'text']:
    with open('../bind/python/libfive/stdlib/%s.py' % m, 'w') as f:
        f.write(format_module(stdlib, m))
        if m in append:
            f.write(append[m])
