"""Extremely experimental code for augmenting pydoctor's data by introspection."""

from pydoctor import model, astbuilder
import types, sys, os, warnings, inspect

def loadModulesForSystem(system):
    """
    Return a dictionary mapping model.Modules to real modules.
    """
    result = {}
    savepath = sys.path[:]
    savefilters = warnings.filters[:]
    mods = sys.modules.copy()
    try:
        sys.path[0:0] = [os.path.dirname(p) for p in system.packages]
        #print sys.path
        warnings.filterwarnings('ignore')
        # this list found by the lovely hack of python -E -c "import sys; print sys.modules.keys()"
        keeps = ['copy_reg', '__main__', 'site', '__builtin__',
                 'encodings', 'posixpath', 'encodings.codecs', 'os.path',
                 '_codecs', 'encodings.exceptions', 'stat', 'zipimport',
                 'warnings', 'encodings.types', 'UserDict', 'encodings.ascii',
                 'sys', 'codecs', 'readline', 'types', 'signal', 'linecache',
                 'posix', 'encodings.aliases', 'exceptions', 'os']
        keeps = dict([(k, sys.modules[k]) for k in keeps if k in sys.modules])
        sys.modules.clear()
        sys.modules.update(keeps)

        verbosity = system.options.verbosity

        modlist = [system.allobjects[m]
                   for m in astbuilder.toposort([m.fullName() for m in system.objectsOfType(model.Module)],
                                                system.importgraph)]
        errcount = 0

        for i, m in enumerate(modlist):
            try:
                realMod = __import__(m.fullName(), {}, {}, ['*'])
            except ImportError, e:
                errcount += 1
                if verbosity > 0:
                    print "could not import", m.fullName(), e
            except Exception, e:
                errcount += 1
                if verbosity > 0:
                    print "error importing", m.fullName(), e
            else:
                result[m] = realMod

            print '\r', i+1-errcount, '/', len(modlist), 'modules imported',
            print errcount, 'failed',
            sys.stdout.flush()
        print
    finally:
        sys.path[:] = savepath
        warnings.filters[:] = savefilters
        sys.modules.clear()
        sys.modules.update(mods)
    return result


_types = {}

def checker(*typs):
    def _(func):
        for typ in typs:
            _types[typ] = func
        return func
    return _

@checker(types.MethodType)
def methChecker(builder, name, OBJ):
    if OBJ.im_self is None:
        return
    f = builder.pushFunction(name, OBJ.__doc__)
    if builder.system.options.verbosity > 0:
        print '**meth**', builder.current, '*************'
    try:
        argspec = inspect.getargspec(OBJ)
        if argspec[0]:
            del argspec[0][0]
        f.argspec = argspec
    finally:
        builder.popFunction()

@checker(types.TypeType, types.ClassType)
def typeChecker(builder, name, OBJ):
    c = builder.current
    mod = c
    while not isinstance(mod, model.Module):
        mod = mod.parent
    if getattr(OBJ, '__module__', None) != mod.fullName() or getattr(OBJ, '__name__', None) != name:
        return
    cls = builder.pushClass(OBJ.__name__, OBJ.__doc__)
    if builder.system.options.verbosity > 0:
        print '**type**', builder.current, '*************'
    try:
        for BASE in OBJ.__bases__:
            baseName = getattr(BASE, '__name__', '?')
            fullName = getattr(BASE, '__module__', '?') + '.' + baseName
            if baseName in mod._name2fullname:
                cls.rawbases.append(baseName)
            else:
                cls.rawbases.append(fullName)
            cls.bases.append(fullName)
            baseObject = builder.system.objForFullName(fullName)
            cls.baseobjects.append(baseObject)
            if baseObject is not None:
                baseObject.subclasses.append(cls)
        for cls2 in builder.system.objectsOfType(model.Class):
            if cls.fullName() in cls2.bases:
                index = cls2.bases.index(cls.fullName())
                assert cls2.baseobjects[index] is None
                cls2.baseobjects[index] = cls
                cls.subclasses.append(cls2)
        checkDict(builder, OBJ.__dict__)
    finally:
        builder.popClass()

@checker(types.FunctionType)
def funcChecker(builder, name, OBJ):
    if isinstance(builder.current, model.Class):
        cls = builder.current
        if name.startswith('_' + cls.name + '__'):
            origname = name[len(cls.name) + 1:]
            if origname in cls.contents:
                return
    f = builder.pushFunction(name, OBJ.__doc__)
    if builder.system.options.verbosity > 0:
        print '**func**', builder.current, '*************'
    try:
        argspec = inspect.getargspec(OBJ)
        f.argspec = argspec
    finally:
        builder.popFunction()


def checkDict(builder, aDict):
    c = builder.current
    for name in aDict:
        if name in ('__name__', '__doc__', '__file__', '__builtins__', '__module__'):
            continue
        if name in c.contents:
            ob = aDict[name]
            o = c.contents[name]
            if hasattr(ob, '__dict__') and not isinstance(ob, types.FunctionType):
                if builder.system.options.verbosity > 1:
                    print 'pushing', o
                builder.push(o)
                checkDict(builder, ob.__dict__)
                builder.pop(o)
            continue
        if name in c._name2fullname:
            continue
        OBJ = aDict[name]
        checker = _types.get(type(OBJ))
        if checker is not None:
            if builder.system.options.verbosity > 1:
                print 'checking', name, 'in', c
            checker(builder, name, OBJ)

def liveCheck(system, builder=None):
    if builder is None:
        builder = system.defaultBuilder(system)
    realMods = loadModulesForSystem(system)
    for m in realMods:
        if builder.system.options.verbosity > 1:
            print 'checking', m
        builder.push(m)
        checkDict(builder, realMods[m].__dict__)
        builder.pop(m)
    system.state = 'livechecked'
