import py

from time import time as now
from py.__.test.terminal.out import getout 
from py.__.test.representation import Presenter
from py.__.test.outcome import Skipped, Passed, Failed

def getrelpath(source, dest): 
    base = source.common(dest)
    if not base: 
        return None 
    # with posix local paths '/' is always a common base
    relsource = source.relto(base)
    reldest = dest.relto(base)
    n = relsource.count(source.sep)
    target = dest.sep.join(('..', )*n + (reldest, ))
    return target 

from py.__.test.session import Session

class TerminalSession(Session): 
    def __init__(self, config, file=None): 
        super(TerminalSession, self).__init__(config) 
        if file is None: 
            file = py.std.sys.stdout 
        self._file = file
        self.out = getout(file) 
        self._opencollectors = []
        self.presenter = Presenter(self.out, config)

    # ---------------------
    # PROGRESS information 
    # ---------------------
   
    def start(self, colitem):
        super(TerminalSession, self).start(colitem) 
        if self.config.option.collectonly: 
            cols = self._opencollectors
            self.out.line('    ' * len(cols) + repr(colitem))
            cols.append(colitem) 
        else: 
            cls = getattr(colitem, '__class__', None)
            if cls is None:
                return
            for typ in py.std.inspect.getmro(cls):
                meth = getattr(self, 'start_%s' % typ.__name__, None)
                if meth:
                    meth(colitem)
                    break 
            colitem.start = py.std.time.time() 

    def start_Module(self, colitem): 
        if self.config.option.verbose == 0: 
            abbrev_fn = getrelpath(py.path.local('.xxx.'), colitem.fspath)
            self.out.write('%s' % (abbrev_fn, ))
        else: 
            self.out.line()
            self.out.line("+ testmodule: %s" % colitem.fspath) 

    def startiteration(self, colitem, subitems): 
        if (isinstance(colitem, py.test.collect.Module) 
            and self.config.option.verbose == 0 
            and not self.config.option.collectonly): 
            try: 
                sum = 0
                for sub in subitems:
                    sum += len(list(colitem.join(sub)._tryiter()))
            except (SystemExit, KeyboardInterrupt): 
                raise 
            except: 
                self.out.write('[?]')
            else: 
                self.out.write('[%d] ' % sum) 
            return self.out.line 

    def start_Item(self, colitem): 
        if self.config.option.verbose >= 1: 
            if isinstance(colitem, py.test.collect.Item): 
                realpath, lineno = colitem._getpathlineno()
                location = "%s:%d" % (realpath.basename, lineno+1)
                self.out.write("%-20s %s " % (location, colitem._getmodpath()))
  
    def finish(self, colitem, outcome):
        end = now()
        super(TerminalSession, self).finish(colitem, outcome) 
        if self.config.option.collectonly: 
            cols = self._opencollectors 
            last = cols.pop()
            #assert last == colitem, "expected %r, got %r" %(last, colitem)
            return
        colitem.elapsedtime = end - colitem.start 
        if self.config.option.usepdb:
            if isinstance(outcome, Failed): 
                print "dispatching to ppdb", colitem
                self.repr_failure(colitem, outcome) 
                import pdb
                self.out.write('\n%s\n' % (outcome.excinfo.exconly(),))
                pdb.post_mortem(outcome.excinfo._excinfo[2])
        if isinstance(colitem, py.test.collect.Module):
            resultstring = self.repr_progress_module_result(colitem, outcome)
            if resultstring:
                self.out.line(" - " + resultstring)
        if isinstance(colitem, py.test.collect.Item): 
            if self.config.option.verbose >= 1: 
                resultstring = self.repr_progress_long_result(colitem, outcome)
                resultstring += " (%.2f)" % (colitem.elapsedtime,)
                self.out.line(resultstring) 
            else:
                c = self.repr_progress_short_result(colitem, outcome)
                self.out.write(c) 


    # -------------------
    # HEADER information 
    # -------------------
    def header(self, colitems): 
        super(TerminalSession, self).header(colitems) 
        self.out.sep("=", "test process starts")
        option = self.config.option 
        modes = []
        for name in 'looponfailing', 'exitfirst', 'nomagic': 
            if getattr(option, name): 
                modes.append(name) 
        #if self._isremoteoption._fromremote:
        #    modes.insert(0, 'child process') 
        #else:
        #    modes.insert(0, 'inprocess')
        #mode = "/".join(modes)
        #self.out.line("testing-mode: %s" % mode)
        self.out.line("executable:   %s  (%s)" %
                          (py.std.sys.executable, repr_pythonversion()))
        rev = py.__package__.getrev()
        self.out.line("using py lib: %s <rev %s>" % (
                       py.path.local(py.__file__).dirpath(), rev))
    
        if self.config.option.traceconfig or self.config.option.verbose: 

            for x in colitems: 
                self.out.line("test target:  %s" %(x.fspath,))

            conftestmodules = self.config._conftest.getconftestmodules(None)
            for i,x in py.builtin.enumerate(conftestmodules):
                self.out.line("initial conf %d: %s" %(i, x.__file__)) 

            #for i, x in py.builtin.enumerate(py.test.config.configpaths):
            #    self.out.line("initial testconfig %d: %s" %(i, x))
            #additional = py.test.config.getfirst('additionalinfo')
            #if additional:
            #    for key, descr in additional():
            #        self.out.line("%s: %s" %(key, descr))
        self.out.line() 
        self.starttime = now()
  
    # -------------------
    # FOOTER information 
    # -------------------
 
    def footer(self, colitems):
        super(TerminalSession, self).footer(colitems) 
        self.endtime = now()
        self.out.line() 
        self.skippedreasons()
        self.failures()
        self.summaryline()

    # --------------------
    # progress information 
    # --------------------
    typemap = {
        Passed: '.',
        Skipped: 's',
        Failed: 'F',
    }
    namemap = {
        Passed: 'ok',
        Skipped: 'SKIP',
        Failed: 'FAIL',
    }

    def repr_progress_short_result(self, item, outcome):
        for outcometype, char in self.typemap.items():
            if isinstance(outcome, outcometype):
                return char
        else:
            #raise TypeError, "not an Outomce instance: %r" % (outcome,)
            return '?'

    def repr_progress_long_result(self, item, outcome):
        for outcometype, char in self.namemap.items():
            if isinstance(outcome, outcometype):
                return char
        else:
            #raise TypeError, "not an Outcome instance: %r" % (outcome,)
            return 'UNKNOWN'

    def repr_progress_module_result(self, item, outcome):
        if isinstance(outcome, Failed):
            return "FAILED TO LOAD MODULE"
        elif isinstance(outcome, Skipped):
            return "skipped"
        elif not isinstance(outcome, (list, Passed)):
            return "?"

    # --------------------
    # summary information 
    # --------------------
    def summaryline(self): 
        outlist = []
        sum = 0
        for typ in Passed, Failed, Skipped:
            l = self.getitemoutcomepairs(typ)
            if l:
                outlist.append('%d %s' % (len(l), typ.__name__.lower()))
            sum += len(l)
        elapsed = self.endtime-self.starttime
        status = "%s" % ", ".join(outlist)
        self.out.sep('=', 'tests finished: %s in %4.2f seconds' %
                         (status, elapsed))

    def getlastvisible(self, sourcetraceback): 
        traceback = sourcetraceback[:]
        while traceback: 
            entry = traceback.pop()
            try: 
                x = entry.frame.eval("__tracebackhide__") 
            except: 
                x = False 
            if not x: 
                return entry 
        else: 
            return sourcetraceback[-1]
        
    def skippedreasons(self):
        texts = {}
        for colitem, outcome in self.getitemoutcomepairs(Skipped):
            raisingtb = self.getlastvisible(outcome.excinfo.traceback) 
            fn = raisingtb.frame.code.path
            lineno = raisingtb.lineno
            d = texts.setdefault(outcome.excinfo.exconly(), {})
            d[(fn,lineno)] = outcome 
                
        if texts:
            self.out.line()
            self.out.sep('_', 'reasons for skipped tests')
            for text, dict in texts.items():
                for (fn, lineno), outcome in dict.items(): 
                    self.out.line('Skipped in %s:%d' %(fn, lineno+1))
                self.out.line("reason: %s" % text) 
                self.out.line()

    def failures(self):
        if self.config.option.tbstyle == 'no':
            return   # skip the detailed failure reports altogether
        l = self.getitemoutcomepairs(Failed)
        if l: 
            self.out.sep('_')
            for colitem, outcome in l: 
                self.repr_failure(colitem, outcome) 

    def repr_failure(self, item, outcome): 
        excinfo = outcome.excinfo 
        traceback = excinfo.traceback
        #print "repr_failures sees item", item
        #print "repr_failures sees traceback"
        #py.std.pprint.pprint(traceback)
        if item and not self.config.option.fulltrace: 
            path, firstlineno = item._getpathlineno()
            ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
            if ntraceback == traceback:
                ntraceback = ntraceback.cut(path=path)
            traceback = ntraceback.filter()
        if not traceback: 
            self.out.line("empty traceback from item %r" % (item,)) 
            return
        handler = getattr(self.presenter, 'repr_failure_tb%s' % self.config.option.tbstyle)
        handler(item, excinfo, traceback, lambda : self.repr_out_err(item))

    def repr_out_err(self, colitem): 
        for parent in colitem.listchain(): 
            for name, obj in zip(['out', 'err'], parent._getouterr()): 
                if obj: 
                    self.out.sep("- ", "%s: recorded std%s" % (parent.name, name))
                    self.out.line(obj)

def repr_pythonversion():
    v = py.std.sys.version_info
    try:
        return "%s.%s.%s-%s-%s" % v
    except ValueError:
        return str(v)
