r"""
Auto generation of Cython files that deal with polymake properties
and small types

This functions in this file are called by setup.py
"""

from __future__ import print_function

import json
import os


header = "# This file was autogenerated! Do not modify!\n"

handler = """
# {cython} {perl}
cdef extern from "wrap.h" namespace "polymake":
    void pm_give_{cython} "GIVE" ({cppcython}, pm_PerlObject*, string) except +ValueError
    void pm_call_method_{cython} "CALL_METHOD" ({cppcython}, pm_PerlObject*, string) except +ValueError

def give_{cython}(PerlObject perl_object, bytes prop):
    cdef {cython} ans = {cython}.__new__({cython})
    cdef string cprop = prop
    sig_str("error in polymake while accessing property")
    pm_give_{cython}(ans.pm_obj, perl_object.pm_obj, cprop)
    sig_off()
    return ans

def call_method_{cython}(PerlObject perl_object, bytes prop):
    cdef {cython} ans = {cython}.__new__({cython})
    cdef string cprop = prop
    sig_str("error in polymake while accessing method")
    pm_call_method_{cython}(ans.pm_obj, perl_object.pm_obj, cprop)
    sig_off()
    return ans
"""

line_give_map = "    b\"{perl}\"{spacing} : give_{cython},\n"
line_call_method_map = "    b\"{perl}\"{spacing} : call_method_{cython},\n"

decl_imports="""
from libcpp cimport bool
from libcpp.string cimport string

from .defs cimport *
from .pm_object cimport PmObject
"""

class_decl="""
cdef class {cython}({parent}):
    cdef {cppcython} pm_obj
"""

#class_def="""
#from .defs cimport pm_{cython}
#
#cdef extern from "wrap.h" namespace "polymake":
#    void pm_{cython}_repr "WRAP_wrap_OUT" (ostringstream, pm_{cython})
#
#cdef class {cython}:
#    def __repr__(self):
#        cdef ostringstream out
#        pm_{cython}_repr(out, self.pm_obj)
#        return (<bytes>out.str()).decode('ascii')
#"""

# Iteration protocol
# pytype     : SetInt
# pmperltype : Set<Int>
# pmcpptype  : Set<int>
# pysubtype  : int
#
# define a "transformer" as a class method
#     cdef {py_subtype} elt_to_python(self, {pm_subtype} x):
#         return x
# or
#     cdef {py_subtype} elt_to_python(self, {pm_subtype} x):
#         {py_subtype} y = {py_subtype}.__new__({py_subtype})
#         y.pm_obj = x
#         return y
#
# It will greatly simplifies __iter__, __reverse__ and __getitem__

# c : simple container -> defines elt_to_python
# C : complex container -> defines elt_to_python
# l : __len__
# i : __iter__
# r : __reverse__
# g : __getitem__
iter_decl="""
cdef extern from "polymake/{name}.h" namespace "polymake":
    cdef cppclass pm_{cython}_iterator "Entire<const {pmcpptype}>::iterator":
        void next "operator++" ()
        bool at_end()
        {pysubtype} get "operator*" ()
    cdef pm_{cython}_iterator entire_{cython} "entire" (pm_{cython})
"""
iter_def="""
    def __iter__(self):
        cdef pm_{cython}_iterator it = entire_{cython}(self.pm_obj)
        while not it.at_end():
            yield self.elt_to_python(it.get())
            it.next()
"""
len_def="""
    def __len__(self):
        return self.pm_obj.size()
"""
getitem_def="""
    def __getitem__(self, Py_ssize_t i):
        if i < 0:
            i += self.pm_obj.size()
        if i < 0 or i >= pm.obj_size():
            raise KeyError("{cython} index out of range")
        return self.elt_to_python(self.pm_obj.get(i))
"""

from .pm_types import pm_types, caml_to_python, python_to_caml
from collections import defaultdict
all_types = pm_types()
types_by_module = defaultdict(list)
for typ,prop in all_types.items():
    mod = prop["module"]
    if mod is not None:
        types_by_module[mod].append(typ)

# NOTE: The extra_decl below is not very elegant but allows to add extra declaration
# to some module...
extra_decl = {
    'Matrix' : 
"""
cdef class PmMatrix(PmObject):
    cpdef resize(self, int nr, int nc)
    cpdef int rows(self)
    cpdef int cols(self)

    cpdef _set_list_of_lists(self, data)
    cpdef _set_matrix(self, mat)
    cpdef _set_plain_list(self, mat)
"""
}

def write_declarations():
    for pm_mod,types in types_by_module.items():
        cy_mod = caml_to_python(pm_mod)
        spacing = " " * (20 - len(pm_mod))
        print(pm_mod + '.h', spacing, "  --> ", cy_mod + '.pxd')
        with open(os.path.join('polymake', cy_mod + '.pxd'), 'w') as output:
            output.write(header)
            output.write('# ' + cy_mod + ' interfacing ' + pm_mod + '.h\n\n')
            output.write('from .defs cimport *\n')
            output.write('from .pm_object cimport PmObject\n')
            if pm_mod in extra_decl:
                print("module '{}' has extra declarations".format(pm_mod))
                output.write(extra_decl[pm_mod])
            for typ in types:
                output.write(class_decl.format(**all_types[typ]))

def undefined_small_classes():
    import re
    class_def = re.compile('cdef class (?P<pytype>[a-zA-Z]*)\(object\)', re.MULTILINE)
    undef_classes = {}
    for pm_mod,types in types_by_module.items():
        if pm_mod != "none":
            cy_mod = caml_to_python(pm_mod)
            spacing = " " * (20 - len(pm_mod))
            filename = os.path.join('polymake', cy_mod + '.pyx')
            u = set(types)
            with open(filename) as f:
                classes = class_def.findall(f.read())
            u.difference_update(classes)
            undef_classes[pm_mod] = u
    return undef_classes

#def write_undefined_classes():
#    classes = undefined_small_classes()
#    for pm_mod, types in types_by_module.items():
#        cy_mod = caml_to_python(pm_mod)
#        filename = os.path.join('polymake', 'auto_{}.pxi'.format(cy_mod))
#        with open(filename, 'w') as output:
#            output.write(header)
#            for typ in types:
#                if typ in classes[pm_mod]:
#                    output.write(class_def.format(**all_types[typ]))

def write_definitions(filename):
    with open(filename, "w") as output:
        output.write(header)
        output.write(def_imports)
        for opts in all_types.values():
            if opts["module"] is not None:
                output.write(class_def.format(**opts))

def write_handlers(filename):
    r"""
    Write down functions that given a small type
    construct the corresponding Python object with a pointer
    to that small object
    """
    with open(filename, "w") as output:
        output.write(header)
        for opts in all_types.values():
            if opts["module"] is not None:
                output.write(handler.format(**opts))

def write_mappings(filename):
    with open(filename, "w") as output:
        output.write(header)

        output.write("cdef dict auto_property_handlers = {\n")
        for opts in all_types.values():
            if opts["module"] is not None:
                spacing = " "*(40 - len(opts['perl']))
                output.write(line_give_map.format(spacing=spacing, **opts))
        output.write("}\n")

        output.write("cdef dict auto_method_handlers = {\n")
        for opts in all_types.values():
            if opts["module"] is not None:
                spacing = " "*(40 - len(opts['perl']))
                output.write(line_call_method_map.format(spacing=spacing, **opts))
        output.write("}\n")

