from AST import ASTNode, getParseTreeNodes
from TheTypeNode import TypeStruct
import MemberNodes
import TypeNodes
from Environment import Env

# name nodes: contains compID and IDs

# TODO:
# Optional stuff:
# clean up findNode vs getNode (remove findNode if getNode works properly)[optional]
# if sth is None in line Node, don't append to children
# add comments for what is self.typeName at each class (the type name that this node is underneath)
# Necessary:
# 2) checking if remaining compid are instance fields
# a) check if the last ID in the name is a method if methodInvoke == true (might not be necessary) [this is after type checking]


class NameNode(ASTNode):
    def __init__(self, parseTree, methodInvoke, typeName):

        self.parseTree = parseTree
        self.name = "" # str: stores original lex of the name (ID/COMPID)
        self.IDs = [] # a queue of strings: each string is an ID in a COMPID/ID.
        self.prefix = "" # str: stores the currently resolved/identified prefix in the name
        self.prefixLink = None # 3 possible values:
                                # 1) a pointer to an AST node (i.e. the prefix is resolved)
                                # 2) "contain" (prefix isn't resolved but is identified)
                                # 3) None: neither of 1 or 2
        self.methodInvoke = methodInvoke # Bool: true if the last ID in the name is a method invokation
        self.methodName = ""
        self.env = None
        self.typeName = typeName # the name of the class or interface that this node belongs under
        self.children = []
        self.myType = None # will become TypeStruct to tell us what the whole is/returns


        self.name = getParseTreeNodes(["ID", "COMPID"], parseTree)[0].lex
        self.IDs = self.name.split(".")

    # Updates the resolved/identified prefix
    # ASSUMPTION: will never be called when self.IDs is empty
    def addToPrefix(self, node):
        # If self.prefixLink is contain, that means our self.prefix wasn't resolved yet
        if type(self.prefixLink) is str and self.prefixLink == "contain":
            self.prefixLink = node
            return

        # Otherwise, we update both self.prefix and self.prefixLink, since we have now resolved a new ID
        if self.prefix:
            self.prefix += "."
        self.prefix += self.IDs[0]
        self.prefixLink = node
        self.IDs.pop(0)

    # Checks and updates prefix if the next ID in self.IDs is a "this" pointer
    def checkThis(self):
        if not self.IDs:
            return True

        # The scanner makes sure that "this" can only be in the beginning of a compid (e.g. a.b.this.d, or a.b.this are both not allowed)
        if self.IDs[0] == "this":
            typeNode = self.env.getNode(self.typeName, "type")
            self.addToPrefix(typeNode)
            return True
        return False

    # Checks and updates prefix if the next ID in self.IDs is a local variable or a parameter
    def checkLocalVar(self):
        if not self.IDs:
            return True
        ID = self.IDs[0]
        localVarNode = self.env.findNode(ID, "expr")
        if localVarNode:
            self.addToPrefix(localVarNode)
            return True

        return False

    # Checks and updates prefix if the next ID in self.IDs is in the contains set
    # TODO: figure out how to do the resolution after type checking is done
    def checkContains(self, update=False):
        if not self.IDs:
            return True

        ID = self.IDs[0]
        if self.env.findNode(ID, "fieldDcl") or self.env.findNode(ID, "method"):
            self.prefixLink = "contain"
            self.addToPrefix("contain")
            return True
        return False


    # Finding a static field
    def checkStatic(self):
        if not self.IDs:
            return True

        currPrefix = ""
        for index, ID in enumerate(self.IDs):
            if currPrefix:
                currPrefix += "."
            currPrefix += ID

            try:
                typeNode = self.env.getNode(currPrefix, "type")
            except Exception as e:
                continue

            # checking if the next ID is a static field in the class/interface
            if index+1 >= len(self.IDs):
                return False

            # if the next field is not a method inovocation
            # (it could only be a method invocation if self.methodinvoke is true and the next field is the last element in the compID)
            if not (self.methodInvoke and index+1 == len(self.IDs) - 1):
                staticFieldName = self.IDs[index+1]

                typeFieldNode = typeNode.env.getNode(staticFieldName, "fieldDcl")
                if "static" in typeFieldNode.mods:
                    self.prefixLink = typeFieldNode.variableDcl
                    
                    # if it is primitive, then we leave it as a VarDclNode
                    if not self.prefixLink.dclType.myType.isPrimitive:
                        self.prefixLink = self.prefixLink.dclType.myType.typePointer
                    
                    self.prefix = currPrefix + "." + staticFieldName
                    self.IDs = self.IDs[index+2:]
                    return True
                return False
            elif self.methodInvoke and index+1 == len(self.IDs) - 1:
                # TODO set the most recent? is this the correct implementation we want?
                self.prefixLink = typeNode
                self.prefix = currPrefix
                self.IDs = self.IDs[index+1:]
                return True

        return False



    # Finds the shortest prefix that either is a local variable, instance field or a static field.
    # Raises error if no such prefix is found in the environment
    # ASSUMPTION: only the left most nodes in the AST
    #             (e.g. if there's sth like a.b.c().d, there would be 2 parse tree nodes: methodInvoke(a.b.c) and fieldAccess(d),
    #             then disambigName is only called on the methodInvoke node)
    def disambigName(self):
        # Checking if a1 is "this"
        if self.checkThis():
            return

        # Checking if a1 is a local variable
        if self.checkLocalVar():
            return

        # Checking if a1 is in contains set
        if self.checkContains():
            return


        # Checking if the shortest prefix is a static field
        if self.checkStatic():
            return

        raise Exception("ERROR at disambiguating namespace: no prefix of name {} is found in environment.".format(self.name))

    def checkType(self):
        # type checking: go through each prefix and determine what type it is, get that type, and check if that type contains the next access
        # eg: a.b.c.d - disambigName would have already determined what the heck the shortest prefix is for this, so take that (let's say it's a.b) then check type c, see if it contains d, then get d return type and add it to self.myType

        if not self.prefixLink or self.prefixLink == 'contain':
            self.prefixLink = self

        curType = self.prefixLink

        if self.IDs:
            while self.IDs:
                if len(self.IDs) == 1:
                    if self.methodInvoke:
                        if curType.__class__.__name__ in ['ParamNode', 'VarDclNode']:
                            curType = curType.myType.typePointer
                        curType = curType.env.getNode(self.IDs[0], 'method')
                        self.methodName = self.IDs[0]
                    else:
                        if curType.myType and curType.myType.isArray and self.IDs[0] == 'length':
                            self.myType = TypeStruct("int", None)
                            return
                        else:
                            if curType.__class__.__name__ in ['ParamNode', 'VarDclNode']:
                                curType = curType.myType.typePointer
                            curType = curType.env.getNode(self.IDs[0], 'fieldDcl')
                    # for methods, we want to keep prefixLink pointing to the class for getting method later
                else:
                    curType = curType.env.getNode(self.IDs[0], 'fieldDcl')

                self.prefix = self.prefix + "." + self.IDs[0]
                self.IDs.pop(0)

            self.prefixLink = curType

        # if self.prefixLink.__class__.__name__ == 'ParamNode':
        #     from pprint import pprint
        #     pprint(vars(self.prefixLink))
        #     self.prefixLink = self.prefixLink.myType.typePointer

        if self.methodInvoke:
            self.myType = 'TObeResolved'
        else:
            self.myType = self.prefixLink.myType

        if not self.myType:
            # from pprint import pprint
            # pprint(vars(self.prefixLink))
            # pprint(vars(self))
            raise Exception("ERROR: Cannot check type of name {}".format(self.name))