import os.path
import inspect
import sys

# completion types.
TYPE_IMPORT = '0'
TYPE_CLASS = '1'
TYPE_FUNCTION = '2'
TYPE_ATTR = '3'
TYPE_BUILTIN = '4'
TYPE_PARAM = '5'

def _imp(name, log=None):
    try:
        return __import__(name)
    except:
        if '.' in name:
            sub = name[0:name.rfind('.')]

            if log is not None:
                log.AddContent('Unable to import', name, 'trying with', sub)
                # log.AddContent('PYTHONPATH:')
                # log.AddContent('\n'.join(sorted(sys.path)))
                log.AddException()

            return _imp(sub, log)
        else:
            s = 'Unable to import module: %s - sys.path: %s' % (str(name), sys.path)
            if log is not None:
                log.AddContent(s)
                log.AddException()

            raise ImportError(s)


IS_IPY = False
if sys.platform == 'cli':
    IS_IPY = True
    _old_imp = _imp
    def _imp(name, log=None):
        # We must add a reference in clr for .Net
        import clr  # @UnresolvedImport
        initial_name = name
        while '.' in name:
            try:
                clr.AddReference(name)
                break  # If it worked, that's OK.
            except:
                name = name[0:name.rfind('.')]
        else:
            try:
                clr.AddReference(name)
            except:
                pass  # That's OK (not dot net module).

        return _old_imp(initial_name, log)



def GetFile(mod):
    f = None
    try:
        f = inspect.getsourcefile(mod) or inspect.getfile(mod)
    except:
        if hasattr(mod, '__file__'):
            f = mod.__file__
            if f.lower(f[-4:]) in ['.pyc', '.pyo']:
                filename = f[:-4] + '.py'
                if os.path.exists(filename):
                    f = filename

    return f

def Find(name, log=None):
    f = None

    mod = _imp(name, log)
    parent = mod
    foundAs = ''

    if inspect.ismodule(mod):
        f = GetFile(mod)

    components = name.split('.')

    old_comp = None
    for comp in components[1:]:
        try:
            # this happens in the following case:
            # we have mx.DateTime.mxDateTime.mxDateTime.pyd
            # but after importing it, mx.DateTime.mxDateTime shadows access to mxDateTime.pyd
            mod = getattr(mod, comp)
        except AttributeError:
            if old_comp != comp:
                raise

        if inspect.ismodule(mod):
            f = GetFile(mod)
        else:
            if len(foundAs) > 0:
                foundAs = foundAs + '.'
            foundAs = foundAs + comp

        old_comp = comp

    return f, mod, parent, foundAs


def GenerateTip(data, log=None):
    data = data.replace('\n', '')
    if data.endswith('.'):
        data = data.rstrip('.')

    f, mod, parent, foundAs = Find(data, log)
    # print_ >> open('temp.txt', 'w'), f
    tips = GenerateImportsTipForModule(mod)
    return f, tips


def CheckChar(c):
    if c == '-' or c == '.':
        return '_'
    return c

def GenerateImportsTipForModule(obj_to_complete, dirComps=None, getattr=getattr, filter=lambda name:True):
    '''
        @param obj_to_complete: the object from where we should get the completions
        @param dirComps: if passed, we should not 'dir' the object and should just iterate those passed as a parameter
        @param getattr: the way to get a given object from the obj_to_complete (used for the completer)
        @param filter: a callable that receives the name and decides if it should be appended or not to the results
        @return: list of tuples, so that each tuple represents a completion with:
            name, doc, args, type (from the TYPE_* constants)
    '''
    ret = []

    if dirComps is None:
        dirComps = dir(obj_to_complete)
        if hasattr(obj_to_complete, '__dict__'):
            dirComps.append('__dict__')
        if hasattr(obj_to_complete, '__class__'):
            dirComps.append('__class__')

    getCompleteInfo = True

    if len(dirComps) > 1000:
        # ok, we don't want to let our users wait forever...
        # no complete info for you...

        getCompleteInfo = False

    dontGetDocsOn = (float, int, str, tuple, list)
    for d in dirComps:

        if d is None:
            continue

        if not filter(d):
            continue

        args = ''

        try:
            obj = getattr(obj_to_complete, d)
        except:  # just ignore and get it without aditional info
            ret.append((d, '', args, TYPE_BUILTIN))
        else:

            if getCompleteInfo:
                retType = TYPE_BUILTIN

                # check if we have to get docs
                getDoc = True
                for class_ in dontGetDocsOn:

                    if isinstance(obj, class_):
                        getDoc = False
                        break

                doc = ''
                if getDoc:
                    # no need to get this info... too many constants are defined and
                    # makes things much slower (passing all that through sockets takes quite some time)
                    try:
                        doc = inspect.getdoc(obj)
                        if doc is None:
                            doc = ''
                    except:  # may happen on jython when checking java classes (so, just ignore it)
                        doc = ''


                if inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
                    try:
                        args, vargs, kwargs, defaults = inspect.getargspec(obj)
                    except:
                        args, vargs, kwargs, defaults = (('self',), None, None, None)
                    if defaults is not None:
                        start_defaults_at = len(args) - len(defaults)


                    r = ''
                    for i, a in enumerate(args):

                        if len(r) > 0:
                            r = r + ', '

                        r = r + str(a)

                        if defaults is not None and i >= start_defaults_at:
                            default =  defaults[i - start_defaults_at]
                            r += '=' +str(default)


                    others = ''
                    if vargs:
                        others += '*' + vargs

                    if kwargs:
                        if others:
                            others+= ', '
                        others += '**' + kwargs

                    if others:
                        r+= ', '


                    args = '(%s%s)' % (r, others)
                    retType = TYPE_FUNCTION

                elif inspect.isclass(obj):
                    retType = TYPE_CLASS

                elif inspect.ismodule(obj):
                    retType = TYPE_IMPORT

                else:
                    retType = TYPE_ATTR


                # add token and doc to return - assure only strings.
                ret.append((d, doc, args, retType))


            else:  # getCompleteInfo == False
                if inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
                    retType = TYPE_FUNCTION

                elif inspect.isclass(obj):
                    retType = TYPE_CLASS

                elif inspect.ismodule(obj):
                    retType = TYPE_IMPORT

                else:
                    retType = TYPE_ATTR
                # ok, no complete info, let's try to do this as fast and clean as possible
                # so, no docs for this kind of information, only the signatures
                ret.append((d, '', str(args), retType))

    return ret




if __name__ == '__main__':
    # To use when we have some object: i.e.: obj_to_complete=MyModel.objects
    temp = '''
def %(method_name)s%(args)s:
    """
%(doc)s
    """
'''

    for entry in GenerateImportsTipForModule(obj_to_complete):
        import textwrap
        doc = textwrap.dedent(entry[1])
        lines = []
        for line in doc.splitlines():
            lines.append('    ' + line)
        doc = '\n'.join(lines)
        print(temp % dict(method_name=entry[0], args=entry[2] or '(self)', doc=doc))
