import os, sys, getopt, string
import TestSuite



class TestMode:
    def __init__(self,name,default):
        self.name = name
        self.default = default
        self.directory = None
        self.inited = -1

    def setDirectory(self,dir):
        self.directory = dir
        
    def init(self,tester):

        if self.inited == -1:
            self.initied = self._init(tester)
        return self.initied

    def _init(self,tester):
        return 1

    def pre(self,tester):
        if self.name:
            tester.startGroup(self.name)
        self._pre(tester)

    def _pre(self,tester):
        pass

    def post(self,tester):
        if self.name:
            tester.groupDone()
        self._post(tester)

    def _post(self,tester):
        pass
    
class DefaultTestMode(TestMode):
    def __init__(self):
        TestMode.__init__(self,None,1)

class TestNode:
    def __init__(self,file,parent):
        self.full_file = file
        self.parent = parent
        self.children = []

        self.pyFile = string.replace(file[len(os.getcwd())+1:],os.sep,'.')
        self.module = None
        try:
            self.module = __import__(self.pyFile, {}, {}, '*')
        except ImportError:
            pass
        self.modes = []
        if self.module:
            self.modes = getattr(self.module,'MODES',[DefaultTestMode()])
            for m in self.modes:
                if type(m) == type(''):
                    print "Change mode: %s" % m
                else:
                    m.setDirectory(self)


    def findCommand(self,path):
        if path is None:
            return self.findRoot()

        fields = string.split(path,os.sep)
        if not len(fields):
            raise Exception("Invalid Command Path %s" % path)
        for c in self.children:
            if c.name == fields[0]:
                break
        else:
            raise Exception("Invalid Command Step %s" % fields[0])

        if len(fields) == 1:
            return c
        return c.findCommand(string.join(fields[1:],os.sep))

    def findRoot(self):
        if self.parent:
            return self.parent.findRoot()
        return self


    def getModes(self):
        if self.parent:
            return self.parent.getModes() + self.modes
        return self.modes


    def getFullName(self):
        if self.parent:
            pn = self.parent.getFullName()
            if pn == self.findRoot().name:
                pn = ''
            if pn:
                pn = pn + os.sep
            return pn + self.name
        return self.name

    def showCommandTree(self,indent=''):
        for c in self.children:
            print indent + c.name
            c.showCommandTree(indent = indent + '  ')

class TestFile(TestNode):
    def __init__(self,file,parent):
        TestNode.__init__(self,file,parent)
        self.name = os.path.basename(file)

    def _run(self,tester,path,clModes,skips,full):
        if not self.module:
            tester.warning("Unable to import %s\n" % self.full_file)
            return
        if hasattr(self.module,'Test'):
            test = self.module.Test
        else:
            tester.warning("'%s' does not define a Test() function" % self.getFullName())
            return



        tester.startGroup(self.name)
        ogLen = len(tester.groups)
        try:
            test(tester)
        except KeyboardInterrupt:
            tester.error('Ctrl-C detected - testing stopped')
            raise
        except SystemExit:
            raise
        except:
            import traceback
            import cStringIO
            st = cStringIO.StringIO()
            traceback.print_exc(file=st)
            tester.error(st.getvalue())
        if len(tester.groups) < ogLen:
            tester.warning('%s closed too many groups' % self.getFullName())
        elif len(tester.groups) > ogLen:
            while len(tester.groups) != ogLen:
                tester.warning('%s did not close group %s' % (
                    self.name, tester.groups[-1].title))
                tester.groupDone()

        tester.groupDone()


class TestDirectory(TestNode):
    def __init__(self,file,parent):
        TestNode.__init__(self,file,parent)
        self.path = os.path.dirname(file)
        self.name = os.path.basename(file)
        if self.name == '__init__':
            self.name = os.path.basename(os.path.dirname(file))
            


    def _buildCommandTree(self,tester):
        """Get all of the test in this directory"""
        files = []
        dirs = []

        listing = os.listdir(self.path)
        for entry in listing:
            full_name = os.path.join(self.path, entry)
            if entry in ['CVS', '__init__.py']:
                # Automatically ignored files
                continue
            elif os.path.isdir(full_name):
                dirs.append(entry)
            else:
                # A file
                (file, ext) = os.path.splitext(entry)
                if string.lower(ext) == '.py':
                    files.append(file)
        dirs.sort()
        files.sort()

        
        if hasattr(self.module, 'PreprocessFiles'):
            (dirs, files) = self.module.PreprocessFiles(tester, dirs, files)
        

        for d in dirs:
            f = self.path + os.sep + d + os.sep + '__init__'
            newDir = TestDirectory(f,self)
            self.children.append(newDir)
            newDir._buildCommandTree(tester)

        for f in files:
            f = self.path + os.sep + f
            newFile = TestFile(f,self)
            self.children.append(newFile)


        return


    def _run(self,tester,path,clModes,skips,full):
        # Determine the modes
        # if full then we start with all of the modes
        if not self.module:
            tester.warning("Unable to import %s\n" % self.full_file)
            return

        if full:
            useModes = self.modes
        else:
            #See if there are any sepcified in the clModes
            useModes = []
            for m in clModes:
                for om in self.modes:
                    if om.name == m:
                        useModes.append(om)
            if not useModes:
                #Use our defaults
                for om in self.modes:
                    if om.default:
                        useModes.append(om)

        #Now trim the skips
        finalModes = []
        for m in useModes:
            if m.name not in skips:
                finalModes.append(m)

        tester.startGroup(self.name)

        modes = []
        for m in finalModes:
            if m.init(tester):
                modes.append(m)
            
        if not modes:
            tester.warning("No modes for %s" % self.getFullName())
        for m in modes:
            m.pre(tester)

            if path is None:
                for c in self.children:
                    c._run(tester,None,clModes,skips,full)
            else:
                toRun = []
                fields = string.split(path,os.sep)
                useChild = None
                for c in self.children:
                    if c.name == fields[0]:
                        useChild = c
                        break

                if useChild:
                    if len(fields) == 1:
                        nextStep = None
                    else:
                        nextStep = string.join(fields[1:],os.sep)
                    useChild._run(tester,nextStep,clModes,skips,full)
                else:
                    tester.warning("Invalid Step %s" % fields[0])

            m.post(tester)


        tester.groupDone()



class TestWalker(TestDirectory):
    options = [('help', 'h', 'Show detailed help message'),
               ('tree', 't', 'Show sub command tree'),
               ('verbose', 'v', 'Increase display verbosity'),
               ('quiet', 'q', 'Decrease display verbosity'),
               ('mode=', 'm', 'Add mode to default modes to run'),
               ('skip=', 'k', 'Remove a mode from the modes to run'),
               ('full', 'f', 'Shortcut for --mode=all'),
               ('stop', 's', 'Stop on errors'),
               ('nocolor', 'n', 'Disable ANSI color sequences'),
               ('noreport', 'r', 'Disable report generation'),
               ('outfile=', 'o', 'Specify an output file for all results'),
               ]


    def __init__(self,testFile):
        self.basePath = os.path.dirname(os.path.abspath(testFile))
        testFile = os.path.splitext(os.path.abspath(testFile))[0]
        self.originalDir = os.getcwd()
        os.chdir(self.basePath)
        sys.path.append(self.basePath)

        self.verbose = 2
        self.stop_on_error = 0
        self.use_color = 1
        self.clModes = []
        self.skips = []
        self.show_help = 0
        self.full = 0
        self.report = 1
        self.show_tree = 0
        self.closeStdOut = 0

        TestDirectory.__init__(self,testFile,None)
        self.name = 'Top Level'



    def run(self,script_name=None, script_args=None):

        os.chdir(self.basePath)

        if script_name is None:
            self.script_name = sys.argv[0]
        if script_args is None:
            self.script_args = sys.argv[1:]

        self._parseCommandLine()

        tester = TestSuite.TestSuite(self.stop_on_error, self.use_color, self.verbose)
        self._buildCommandTree(tester)
        if not self.parsed_args:
            self.parsed_args = [None]

        if self.show_help:
            self.showHelp()
        elif self.show_tree:
            self.showTree()
        else:
            self.runTests(tester)


        if self.closeStdOut:
            sys.stdout = sys.__stdout__

        os.chdir(self.originalDir)



    def _parseCommandLine(self):

        long_opts = []
        short_opts = ''
        for opt in self.options:
            short_opts = short_opts + opt[1]
            if opt[0][-1] == '=':
                short_opts = short_opts + ':'
            long_opts.append(opt[0])

        args = self.script_args
        self.parsed_args = []
        parsed_opts = []
        while args:
            optlist, args = getopt.getopt(args, short_opts, long_opts)
            while args and args[0][0] != '-':
                arg = os.path.normpath(args[0])
                self.parsed_args.append(arg)
                del args[0]
            parsed_opts.extend(optlist)


        fileName = None
        
        # At this point we now have all options and args grouped seperately
        for (name, value) in parsed_opts:
            if name in ['-v', '--verbose']:
                self.verbose = self.verbose + 1
            elif name in ['-q', '--quiet']:
                self.verbose = self.verbose - 1
            elif name in ['-m', '--mode']:
                self.clModes.append(value)
            elif name in ['-k', '--skip']:
                self.skips.append(value)
            elif name in ['-f','--full']:
                self.full = 1
            elif name in ['-s', '--stop']:
                self.stop_on_error = 1
            elif name in ['-n', '--nocolor']:
                self.use_color = 0
            elif name in ['-h', '--help']:
                self.show_help = 1
            elif name in ['-t', '--tree']:
                self.show_tree = 1
            elif name in ['-r', '--noreport']:
                self.report = 0
            elif name in ['-o', '--outfile']:
                fileName = value

        if fileName:
            sys.stdout = open(fileName,'w')
            self.closeStdOut = 1

        newArgs = []
        for p in self.parsed_args:
            (file, ext) = os.path.splitext(p)
            if string.lower(ext) == '.py':
                newArgs.append(file)
            else:
                newArgs.append(p)
        self.parsed_args = newArgs
        return


    def showTree(self):
        print 'Test Command Tree:'
        print 
        self.showCommandTree()

    def showHelp(self):
        print 'Usage:'
        print '  %s [options] [test_spec]' % self.script_name
        print
        print 'Options'
        print '-------'
        display_opts = []
        max_opt = 0
        for opt in self.options:
            long = opt[0]
            if long[-1] == '=':
                long = '%s<%s>' % (long, long[:-1])
            display = '-%s, --%s' % (opt[1], long)
            display_opts.append((display, opt[2]))
            if len(display) > max_opt:
                max_opt = len(display)
        for display, help in display_opts:
            print '  %-*s  %s' % (max_opt, display, help)


        for test in self.parsed_args:
            testCmd = self.findCommand(test)
            modes = testCmd.getModes()
            title = "Modes for %s" % testCmd.getFullName()
            print
            print title
            print '-'*len(title)
            for mode in modes:
                if mode.name:
                    print "  %s (from %s)" % (mode.name,mode.directory.getFullName())

            print
            title = "Sub Tests for %s" % testCmd.getFullName()
            print title
            print '-'*len(title)
            print
            for child in testCmd.children:
                print "  %s" % child.name
            
        print
        print

        
    def runTests(self,tester):
        for path in self.parsed_args:
            self._run(tester,path,self.clModes,self.skips,self.full)
        if self.report:
            tester.report()
        
