import subprocess
import time
import importlib
import inspect
import pkgutil


class ModulCrawler:
    def __init__(self):
        self.module_dictionaries = {}

    def module_names(self, package):
        mods = []
        for importer, modname, ispkg in pkgutil.walk_packages(path=package.__path__,
                                                              prefix=package.__name__+'.',
                                                              onerror=lambda x: None):
            mods.append(modname)
        public_mods = [mod for mod in mods if '._' not in mod ] # ignoring private modules
        return public_mods

    def fqname(function):
        return function.__module__ + '.' + function.__qualname__

    def get_functions(self, library):
        functions = []

        for module in self.module_names(library):
            try:
                funobjs = inspect.getmembers(importlib.import_module(module), inspect.isfunction)
            except: # catch-all, as plenty of different errors can happen while importing deprecated modules
                continue
            for funobj in funobjs:
                functions.append(self.fqname(funobj[1]))

        public_functions = set([fun for fun in functions if '._' not in fun ]) # ignoring private functions
        return public_functions

    def add_funcs(self, function_lookup, func_list):
        for func in func_list:
            cur_dict = function_lookup
            for part in reversed(func.split('.')):
                if part not in cur_dict:
                    cur_dict[part] = {}
                cur_dict = cur_dict[part]
        return function_lookup

    def add_module(self, module):
        if module not in self.module_dictionaries:
            function_names = get_function(module)
            print('Added module',str(module),'which has',str(len(function_names)),'functions')
            self.module_dictionaries[module] = function_names

    def get_combined_dictionary(self, group_of_modules):
        for module in group_of_modules:
            self.add_module(module)
        result = {}
        for module in group_of_modules:
            func_list = self.module_dictionaries[module]
            self.add_funcs(result, func_list)
        return result

class GetDocstrings:
    def __init__(self):
        self.mc = ModulCrawler()

    def transform(self, corpus):
        # assuming already ran get_imports
        cur_docstrings = {}
        start = time.time()
        self.corpus = corpus
        for index in range(corpus.get_number_notebooks()):
            cur_docstrings = self.set_docstring(index, cur_docstrings)
        end = time.time()
        print("Time Elapsed", end - start, "for", corpus.get_number_notebooks(), 'notebooks') 
        return self.corpus


    def set_docstring(self, index, cur_docstrings = {}):
        for seg in self.corpus.get_notebook_segments(index):
            functions = seg.get_feature('full_name_string')
            print(functions)
            #if functions != []:
            #   import pdb; pdb.set_trace()
            docstrings = []
            for func in functions:
                if func in cur_docstrings:
                    docstrings.append(cur_docstrings[func])
                    continue

                if len(func.split('.')) == 1:
                    new_val = subprocess.check_output(['pydoc',func])
                    cur_docstrings[func] = new_val
                    docstrings.append(new_val)
                    #docstrings.append(eval(func+'.__doc__'))

                elif len(func.split('.')) > 1:
                    # cannot add anything if we have a function called on a variable
                    if func.split('.')[0] == 'var':
                        continue
                    new_val = subprocess.check_output(['pydoc',func]).decode('utf-8')
                    if new_val[:23].lower() == 'no python documentation':
                        pass

                    cur_docstrings[func] = new_val
                    docstrings.append(new_val)

            seg.put_feature('docstrings', docstrings)
        return cur_docstrings

    def get_docstrings(self):
        all_docs = []
        for index in range(self.corpus.get_number_notebooks()):
            nb_imports = self.corpus.get_feature('imports')
            self.mc.get_combined_dictionary([imp.split('.')[0] for imp in nb_imports])
            for a in self.corpus.get_notebook_segments(index):
                all_docs.append(a.get_feature('docstrings'))
        return all_docs


