"""

class prototype_collector generates code to wrap functions using only numerical
arguments as UFuncs. This involves the following steps:

    - searching for the header files and parsing them
    - generating code required for the different functions

    - writing the code for the different UFunc evaluators required for the
      different types.
      
"""
from __future__ import print_function
import re
import string
import os.path
from ufunc import PyUFunc



class prototype_collector:
    """
    collects special function prototypes from headers
    """

    # Check if the name indicates a ufunc wrapping around a function using
    # doubles.
    fake_test = re.compile("as")
    def __init__(self,prefix=None):
        self.prefix=prefix
        self.exclude_list = []

    def set_exclude_list(self, mylist):
        "The functions not to include in the list"
        self.exclude_list = mylist

    def make_wrapper_and_index_file(self, filename, *search_patterns):
        """
        The main worker.
        
        Reads the header files using get_prototypes. This sets up a
        representation of the different prototypes as PyUFunc objects. 
        These objects are then used to write the different parts of the
        wrapper. The files for the different parts are listed below:
        
        _index.c    : entries for the PyMethodstable for the sf module
        _data.c     : static data for the different Ufunc evaluators
        _ufuncs.txt : lists which ufunc is used to evaluate which function
        _evals.c    : the different evaluators

        First it writes the (static) information for wrapping the different functions.

        Then it generates a list of the different required UFunc evaluation functions
        and generates the code for them. Each evaluator can be used for all the different
        functions with the same prototype.
        
        """
        prototypes=self.get_prototypes(*search_patterns)
        index_file = file(filename+"_index.c","w")
        data_file = file(filename+"_data.c","w")
        objects_file = file(filename+"_objects.c","w")
        ufuncs_list =  file(filename+"_ufuncs.txt","w")
        evaluators = file(filename + "_evals.c", "w")
        ufunc_names_dic = {}

        for p in  prototypes[:]:
            if p.get_name() in self.exclude_list:
                # Skip the excluded functions            
                continue
            
            index_entry=p.make_function_entry()
            if index_entry is not None:
                try:
                    a_py_ufunc =  p.make_wrapper_function()
                    data_file.write(a_py_ufunc.WriteStaticData()+"\n")
                    #objects_file.write("{\n")
                    test = 0
                    try:
                        objects_file.write(a_py_ufunc.WriteUFuncObject()+"\n")
                        test = 1
                    finally:
                        if test == 0:
                            print( "I was working on prototype", p.get_name())
                    #objects_file.write("}\n")
                    index_file.write(index_entry+",\n")
                except RuntimeError as message:
                    print(message)

                # Collect the various differnt  ufuncs objects. These will
                # iterate over the functions
                ufunc_names = a_py_ufunc.GetPyUFuncEvaluatorNames()    
                for name in ufunc_names:
                    print (name)
                    if not ufunc_names_dic.has_key(name):
                        ufunc_names_dic[name] = []
                    ufunc_names_dic[name].append(a_py_ufunc)                  
            else:
                print ("No index entry for", p.get_name())
                    
        index_file.close()

        # List which evaluator handles which functions
        names = ufunc_names_dic.keys()
        names.sort()
        for i in names:
            ufuncs_list.write(i + ": ");
            l = map(lambda x: x.GetName(), ufunc_names_dic[i])
            l.sort()
            ufuncs_list.write(string.join(l, ", "))
            ufuncs_list.write("\n")

            
        for methods in ((objects_file, lambda x: x.WriteUFuncObjectHelpersArrayData(),),
                        (data_file, lambda x: x.WriteUFuncObjectHelpers(),),                        
                        (evaluators, lambda x: x.GetTypeDef(), lambda x: x.GetEvaluator())):
            for i in names:
                fake = self.fake_test.search(i)
                obj = ufunc_names_dic[i][0]
                if not fake:        
                    #evaluators.write("\n/* obj name = ->%s<-, name = ->%s<- */\n" % (obj.GetName(), i))
                    for m in methods[1:]:                        
                        methods[0].write(m(obj) + "\n")
                        
        objects_file.close()
        evaluators.close()
        data_file.close()
        
        documentation_file = file(filename + "_doc.tex", "w")
        all_functions = {}
        for i in names:
            l = ufunc_names_dic[i]
            for uf in l:
                all_functions[uf.GetName()] = uf
        keys = all_functions.keys()
        keys.sort()
        for key in keys:
            uf = all_functions[key]
            doc = uf.WriteUFuncLatexDoc()
            documentation_file.write(doc)
        documentation_file.close()
        
    def get_prototypes(self, *patterns):
        """
        Get the function prototypes from the files
        """
        file_name_list=self.determine_headers(*patterns)
        all_prototypes=[]
        for file_name in file_name_list:
            print( "Processing file %s" %(file_name, ))
            new_prototypes=self.get_prototypes_from_file(file_name)
            all_prototypes.extend(new_prototypes)
        return all_prototypes
        
    def determine_headers(self, *patterns):
        """
        returns a list of header files of special functions.
        
        searches for the header file "<gsl/gsl_sf.h>" for the special functions
        of the GSL library and generates a list of all the gsl_sf_*.h files
        included there.
        """

        #prepare file names
        general_include_dir_name=self.prefix

        sf_header_list=[]
        for pattern in patterns:
            general_include_file_name=os.path.join(general_include_dir_name, "gsl", pattern)

            #test if exists
            if not os.path.isfile(general_include_file_name):
                raise Exception("could not find general header file '%s'" % (general_include_file_name,))

            #parse file for includes
            general_include_file=file(general_include_file_name,"r")
            include_pattern=re.compile("#\s*include\s+[\"<](.+)[\">].*")
            a_line=general_include_file.readline()
            while a_line:
                # if line is matching
                include_match=include_pattern.match(a_line)
                if include_match:
                    new_header_name=os.path.join(general_include_dir_name,include_match.group(1))
                    if os.path.isfile(new_header_name):
                        sf_header_list.append(new_header_name)
                    else:
                        print ("could not find header %s",new_header_name)

                a_line=general_include_file.readline()
            general_include_file.close()
            sf_header_list.append(general_include_file_name)
        return sf_header_list


    def get_prototypes_from_file(self, include_file_name):
        """
        searches for prototypes from a given file and creates a list of prototype objects
        """

        # read file
        include_file=file(include_file_name,"r")
        contents=include_file.read()
        include_file.close()

        # skip begin and end
        decl_only_pattern=re.compile(".*__BEGIN_DECLS(.*)__END_DECLS.*",re.MULTILINE+re.DOTALL)
        decls_match=decl_only_pattern.match(contents,re.MULTILINE+re.DOTALL)
        contents=None # forget whole contents
        if not decls_match:
            return []

        # find declarations section
        declaration_contents=decls_match.group(1)
        # find declaration and preceeding comment
        function_pattern=re.compile("(?:/\*.*?\*/\s*)?\w+\s+\w+\s*\([^\)]*\)\s*;",
                                    re.MULTILINE+re.DOTALL)
        match_list=function_pattern.findall(declaration_contents,
                                            re.MULTILINE+re.DOTALL)
        #parse all declarations
        prototype_list=[]
        for match in match_list:            
            tmp = sf_prototype(match)
            msg = "Found function prototype " + tmp.get_name()
            if tmp.get_name() not in self.exclude_list:
                prototype_list.append(tmp)
            else:
                msg += " excluded"
                print(msg)
        return prototype_list
                
class sf_prototype:
    """
    contains all necessary informations about the prototype
    and can contain code for it
    """

    def __init__(self,characters, use_return_value = 0):
        #parse all declarations
        declaration_pattern=re.compile("(?P<comment>/\*.*?\*/\s*)?\s*(?P<return_value>\w+\s+)(?P<name>\w+\s*)\((?P<parameters>[^\)]*)\)\s*;")
        declaration_match=declaration_pattern.search(characters)
        if declaration_match is None:
            raise Exception( "could not evaluate definition")

        # prepare comment for __doc__
        comment=declaration_match.group('comment')
        # todo
        self.comment=comment
        
        # save return value
        self.return_value=string.strip(declaration_match.group('return_value'))

        # save function name
        self.name=string.strip(declaration_match.group('name'))

        # split and save parameters
        func_parameters=string.strip(declaration_match.group('parameters'))
        self.parameters=re.split("\s*,\s*",func_parameters)

        self.use_return_value = 0

    def get_name(self):
        return self.name
    
    def to_string(self):
        """
        prints this object
        """
        return self.original_declaration()

    def original_declaration(self):
        """
        representation as declaration without comment
        """
        outstring=self.return_value+" "+self.name+"("
        if self.parameters:
            for p in self.parameters[:-1]:
                outstring+=p+", "
            outstring+=self.parameters[-1]
        return outstring+");"

    def make_function_entry(self):
        """
        returns the entry for the function index {name, function_name, argument method}
        """

        # First test if this function uses a flag to return error.
        matched_name_stem=re.match("gsl_sf_(.+?_e)$",self.name)
        if matched_name_stem is None:
            matched_name_stem=re.match("gsl_sf_(.+?)$",self.name)
            self.use_return_value = 1
        if matched_name_stem is None:
            # Try if it is a complex function
            matched_name_stem=re.match("gsl_complex_(.+?)$",self.name)
            self.use_return_value = 1            
        if  matched_name_stem is None:
            return None
        name_stem=matched_name_stem.group(1)
        return "{\"%s\",%s_wrap,METH_VARARGS}"%(name_stem,self.name)
        

    def make_wrapper_function(self):
        """
        define function for gsl wrapper library using PyUFunc objects
        """


        a_pyufunc = PyUFunc()
        a_pyufunc.ReturnIsErrorFlag(not self.use_return_value)
        a_pyufunc.SetName(self.name)
        
        argument_type_pattern=re.compile("((?P<qualifier>const)\s+)?(?P<type>(unsigned\s+)?\w+)\s*(?P<operator>\*|&)?\s*(?P<name>\w+)")

        parameter_string=""         # the parameter list for gsl function
        arg_parse_type=""           # the argument pattern for PyArg_ParseTuple
        arg_parse_name=""           # the argument list for PyArg_ParseTuple 
        arg_decl=""                 # declaration section
        arg_control=""              # code parts for parameter check
        result_arg=""               # is the argument list of Py_BuildValue 
        result_format=""            # is the formatstring of Py_BuildValue


        
        a_pyufunc.ReturnParam("return", self.return_value, "")
            
        for p in self.parameters:
            arg_match=argument_type_pattern.match(p)
            if arg_match is None:
                raise RuntimeError("%s: can not interpret parameter %s"%(self.name, p))
            arg_type=arg_match.group('type')
            arg_name=arg_match.group('name')
            arg_qualifier=arg_match.group('qualifier')
            arg_operator=arg_match.group('operator')
            if arg_operator == "*":
                a_pyufunc.AddOutParams(arg_name, arg_type, arg_operator)
            else:
                a_pyufunc.AddInParams(arg_name, arg_type, arg_operator)
            
        func="/* wrapper for "+self.original_declaration()+"  return = "+ self.return_value + " */\n" 

        a_pyufunc.AllParametersHandled()
        return a_pyufunc
