# This module contains AST nodes for Source files, Packages and the
# Root of the parse tree.  It's called extended_ast because it extends
# the normal ast to support multiple sourcenodes.

from compiler.ast import *
import compiler
import os
from bike.parsing.fastparser import fastparser
import re
import StringIO
from time import time
from stat import ST_MTIME
from bike.parsing.save import queueFileToSave, resetOutputQueue


_root = None

def getRoot():
    global _root
    if _root is None:
        resetRoot()
    return _root 

def resetRoot(root = None):
    global _root
    _root = root or Root()
    _root.unittestmode = False
    resetOutputQueue()

            
class Root:
    # Always the root of the extended ast.
    nodes['root'] = 'Root'

    def __init__(self, packages = None, modules = None, pythonpath = None):
        # singleton hack to allow functions in query package to appear
        # 'stateless'
        resetRoot(self)

        # this is to get round an optimisation which reuses an empty
        # list as a default arg. unfortunately the client of this
        # method may fill that list, so it's not empty
        if not packages:
            packages = []
        if not modules:
            modules = []
        if not pythonpath:
            pythonpath = []
        self.fqn = ""
        self.pythonpath = pythonpath
        self._children = ('top', packages, modules, pythonpath)
        for package in packages:
            package.setParent(self)
        for mod in modules:
            mod.setParent(self)

    def __repr__(self):
        return "Root()"
        #return "Root(%s)"%(self.getChildNodes())


    def getChild(self,name):
        return None

    def getChildNodes(self):
        return []

    def getSourceNode(self,filename):
        filename = os.path.normpath(os.path.abspath(filename))
        class SourceVisitor:
            srcnode = None
            def visitSource(self, node):
                if node.filename == filename:
                    self.srcnode = node
        return compiler.walk(self, SourceVisitor()).srcnode


class Package:
    nodes['package'] = 'Package'

    def __init__(self, path, name):
        self.path = path
        self.name = name
        #self._children = self.packages, self.modules

    def setFQN(self, fqn):
        self.fqn = fqn

    def setParent(self, scope):
        self.parentScope = scope

    def getParent(self):
        return self.parentScope

    def getChild(self,name):
        from bike.parsing.newstuff import getModule
        return getModule(os.path.join(self.path,name+".py"))

    
    def getChildNodes(self):
        nodes = []
        if self.packages is not None: nodes+=self.packages
        if self.modules is not None: nodes+=self.modules
        return tuple(nodes)

    def __repr__(self):
        return "Package(%s,%s)"%(self.path, self.name)
        
wordRE = re.compile("\w+")
class Source:
    nodes['Source'] = 'Source'

    def createFromString(filename, modulename, src):
        return Source(filename,modulename,src, int(time()))
    createFromString = staticmethod(createFromString)

    def createFromFile(filename,modulename):
        try:
            f = file(filename)
            src = f.read()
            f.close()
        except IOError:
            return None
        else:
            return Source(filename,modulename,src,
                          int(os.stat(filename).st_mtime))
                
    createFromFile = staticmethod(createFromFile)
    
    def __init__(self, filename, modulename, src, mtime):

        if os.path.isabs(filename):
            self.filename = filename
        else:
            self.filename = os.path.abspath(filename)
        self.modulename = modulename

        self.load(src)
        self.lastModified = mtime 

    def load(self, source):
        # fastparser ast
        self.fastparseroot = fastparser(source,self.modulename,self.filename)
        self.fastparseroot.setSourceNode(self)
        self._lines = source.splitlines(1)
        self.sourcenode = self

    def __repr__(self):
        return "Source(%s,%s)"%('source', self.filename)

    def getChildren(self):
        return self.getChildNodes()

    def getChildNodes(self):
        return self.getClassesAndFunctions()

    def reload(self):
        if os.stat(self.filename).st_mtime > self.lastModified:
            f = file(self.filename)
            newsrc = f.read()
            self.load(newsrc)
            f.close()
            self.setParent(self.parentScope)
            

    def updateWithSource(self,source):
        self.load(source)
        if hasattr(self,"parentScope"):
            self.setParent(self.parentScope)
        
    def setFQN(self, fqn):
        self.fqn = fqn

    def setParent(self, scope):
        self.parentScope = scope
        
        if self.parentScope.fqn == "":
            self.setFQN(self.modulename)
        else:
            self.setFQN(self.parentScope.fqn+"."+self.modulename)

        self.fastparseroot.setParent(scope)
        
    def getSource(self):
        return  "".join(self.getLines())

    def getLine(self,linenum):
        return self.getLines()[linenum-1]

    def getClassesAndFunctions(self):
        return self.fastparseroot.getChildNodes()

    # TODO: rename me!
    def getFlattenedListOfFastParserASTNodes(self):
        return self.fastparseroot.getFlattenedListOfChildNodes()
        
    def containsString(self,string):
        if self.getSource().find(string) != -1:
            #print self.getLines()
            for line in self.getLines():
                if line.find(string) != -1:
                    words = wordRE.findall(line)
                    if string in words:
                        return 1
        return 0

    def getLines(self):
        return self._lines

# describes an instance of something (e.g. instance of a class)
class Instance(compiler.ast.Node):
    def __init__(self, type):
        assert type is not None
        self._type = type

    def getType(self):
        return self._type

    def __str__(self):
        return "Instance(%s)"%(self.getType())



# Local Variables:          
# mode:python               
# indent-tabs-mode: nil     
# py-indent-offset: 4   
# py-smart-indentation: nil 
# End:                      
