# getTypeOf(scope,fqn) and getTypeOfExpr(scope,ast)

from bike.parsing.fastparserast import Class, Function, Module, Root, getRoot, Package, Instance, getModule
from bike.parsing.parserutils import generateLogicalLines, makeLineParseable,splitLogicalLines, makeLineParseable
from bike.parsing import visitor
from bike import log
from bike.parsing.newstuff import getModuleOrPackageUsingFQN
from bike.parsing.pathutils import getPackageBaseDirectory
from bike.parsing.load import Cache
import os
import re
import compiler

# used if an assignment exists, but cant find type
# e.g. a = SomeFunctionNotLoaded()
# (as opposed to 'None' if cant find an assignment)
class UnfoundType: pass


getTypeOfStack = []

# name is the fqn of the reference, scope is the scope ast object from
# which the question is being asked.
# returns an fastparser-ast object representing the type
# or None if type not found
def getTypeOf(scope, fqn):
    if isinstance(scope, Root):
        assert False, "Can't use getTypeOf to resolve from Root. Use getModuleOrPackageUsingFQN instead"


    #print "getTypeOf:"+fqn+" -- "+str(scope)
    #print 
    #print str(getTypeOfStack)
    #print 
    if (fqn,scope) in getTypeOfStack:   # loop protection
        return None

    # this is crap!
    hashcode = str(scope)+fqn

    try:
        getTypeOfStack.append((fqn,scope))

        try:
            type = Cache.instance.typecache[hashcode]
        except KeyError:
            type = getTypeOf_impl(scope, fqn)
            Cache.instance.typecache[hashcode] = type
        return type
    finally:
        del getTypeOfStack[-1]
        


def getTypeOf_impl(scope, fqn):
    #print "getTypeOf_impl",scope,fqn
    if fqn == "None":
        return None

    if "."in fqn:
        rcdr = ".".join(fqn.split(".")[:-1])
        rcar = fqn.split(".")[-1]
        newscope = getTypeOf(scope,rcdr)
        if newscope is not None:
            return getTypeOf(newscope, rcar)
        else: 
            #print "couldnt find "+rcdr+" in "+str(scope)
            pass

    assert scope is not None
    #assert not ("." in fqn) 
    
    if isinstance(scope,UnfoundType):
        return UnfoundType()
    
    if isinstance(scope, Package):
        #assert 0,scope
        return handlePackageScope(scope, fqn)
    elif isinstance(scope,Instance):
        return handleClassInstanceAttribute(scope, fqn)
    else:
        return handleModuleClassOrFunctionScope(scope,fqn)



def handleModuleClassOrFunctionScope(scope,name):
    if name == "self" and isinstance(scope,Function) and \
           isinstance(scope.getParent(),Class):
        return Instance(scope.getParent())
    
    matches = [c for c in scope.getChildNodes()if c.name == name]
    if matches != []:
        return matches[0]
    
    type = scanScopeSourceForType(scope, name)
    if type != None:
        return type
    
    #print "name = ",name,"scope = ",scope
    type = getImportedType(scope, name)   # try imported types
    #print "type=",type
    if type != None:
        return type
    parentScope = scope.getParent()
    while isinstance(parentScope,Class):
        # don't search class scope, since this is not accessible except
        # through self   (is this true?)
        parentScope = parentScope.getParent()

    if not (isinstance(parentScope,Package) or isinstance(parentScope,Root)):
        return getTypeOf(parentScope, name)


def handleClassInstanceAttribute(instance, attrname):
    theClass = instance.getType()

    # search methods and inner classes
    match = theClass.getChild(attrname)
    if match:
        return match

    #search methods for assignments with self.foo getattrs
    for child in theClass.getChildNodes():
        if not isinstance(child,Function):
            continue
        res = scanScopeAST(child,attrname,
                          SelfAttributeAssignmentVisitor(child,attrname))
        if res is not None:
            return res

def handlePackageScope(package, fqn):
    #print "handlePackageScope",package,fqn
    child = package.getChild(fqn)
    if child: