
""" Rest reporting stuff
"""

import py
import sys
from StringIO import StringIO
from py.__.test.rsession.reporter import AbstractReporter
from py.__.test.rsession import repevent
from py.__.rest.rst import *

class RestReporter(AbstractReporter):
    linkwriter = None

    def __init__(self, *args, **kwargs):
        super(RestReporter, self).__init__(*args, **kwargs)
        self.rest = Rest()
        self.traceback_num = 0
    
    def get_linkwriter(self):
        if self.linkwriter is None:
            try:
                self.linkwriter = self.config.getvalue('linkwriter')
            except KeyError:
                print >>sys.stderr, ('no linkwriter configured, using default '
                                     'one')
                self.linkwriter = RelLinkWriter()
        return self.linkwriter
        
    def report_unknown(self, what):
        if self.config.option.verbose:
            self.add_rest(Paragraph("Unknown report: %s" % what))

    def gethost(self, item):
        if item.channel:
            return item.channel.gateway.host
        return self.hosts[0]

    def report_SendItem(self, item):
        address = self.gethost(item).hostname
        if self.config.option.verbose:
            self.add_rest(Paragraph('sending item %s to %s' % (item.item,
                                                               address)))

    def report_HostRSyncing(self, item):
        self.add_rest(LiteralBlock('%10s: RSYNC ==> %s' % (item.host.hostname[:10],
                                                        item.host.relpath)))

    def _host_ready(self, item):
        self.add_rest(LiteralBlock('%10s: READY' % (item.host.hostname[:10],)))

    def report_TestStarted(self, event):
        txt = "Running tests on hosts: %s" % ", ".join([i.hostname for i in
                                                        event.hosts])
        self.add_rest(Title(txt, abovechar='=', belowchar='='))
        self.timestart = event.timestart

    def report_TestFinished(self, item):
        self.timeend = item.timeend
        self.summary()
        return len(self.failed_tests_outcome) > 0

    def report_ImmediateFailure(self, item):
        pass

    def report_HostGatewayReady(self, item):
        self.to_rsync[item.host] = len(item.roots)

    def report_ItemStart(self, event):
        item = event.item
        if isinstance(item, py.test.collect.Module):
            lgt = len(list(item._tryiter()))
            lns = item.listnames()[1:]
            name = "/".join(lns)
            link = self.get_linkwriter().get_link(self.get_rootpath(item),
                                                  item.fspath)
            if link:
                name = Link(name, link)
            txt = 'Testing module %s (%d items)' % (name, lgt)
            self.add_rest(Title('Testing module', name, '(%d items)' % (lgt,),
                                belowchar='-'))
    
    def get_rootpath(self, item):
        root = item.parent
        while root.parent is not None:
            root = root.parent
        return root.fspath

    def print_summary(self, total, skipped_str, failed_str):
        self.skips()
        self.failures()

        txt = "%d tests run%s%s in %.2fs (rsync: %.2f)" % \
            (total, skipped_str, failed_str, self.timeend - self.timestart,
             self.timersync - self.timestart)
        self.add_rest(Title(txt, belowchar='-'))

        # since we're rendering each item, the links haven't been rendered
        # yet
        self.out.write(self.rest.render_links())

    def report_ReceivedItemOutcome(self, event):
        host = self.gethost(event)
        if event.outcome.passed:
            status = [Strong("PASSED")]
            self.passed[host] += 1
        elif event.outcome.skipped:
            status = [Strong("SKIPPED")]
            self.skipped_tests_outcome.append(event)
            self.skipped[host] += 1
        else:
            status = [Strong("FAILED"),
                      InternalLink("traceback%d" % self.traceback_num)]
            self.traceback_num += 1
            self.failed[host] += 1
            self.failed_tests_outcome.append(event)
            # we'll take care of them later
        itempath = self.get_path_from_item(event.item)
        status.append(Text(itempath))
        hostname = host.hostname
        self.add_rest(ListItem(Text("%10s:" % (hostname[:10],)), *status))

    def skips(self):
        # XXX hrmph, copied code
        texts = {}
        for event in self.skipped_tests_outcome:
            colitem = event.item
            if isinstance(event, repevent.ReceivedItemOutcome):
                outcome = event.outcome
                text = outcome.skipped
                itemname = self.get_item_name(event, colitem)
            elif isinstance(event, repevent.SkippedTryiter):
                text = str(event.excinfo.value)
                itemname = "/".join(colitem.listnames())
            if text not in texts:
                texts[text] = [itemname]
            else:
                texts[text].append(itemname)
        if texts:
            self.add_rest(Title('Reasons for skipped tests:', belowchar='+'))
            for text, items in texts.items():
                for item in items:
                    self.add_rest(ListItem('%s: %s' % (item, text)))

    def get_host(self, event):
        try:
            return event.channel.gateway.host
        except AttributeError:
            return None

    def failures(self):
        self.traceback_num = 0
        tbstyle = self.config.option.tbstyle
        if self.failed_tests_outcome:
            self.add_rest(Title('Exceptions:', belowchar='+'))
        for i, event in enumerate(self.failed_tests_outcome):
            if i > 0:
                self.add_rest(Transition())
            if isinstance(event, repevent.ReceivedItemOutcome):
                host = self.get_host(event)
                itempath = self.get_path_from_item(event.item)
                root = self.get_rootpath(event.item)
                link = self.get_linkwriter().get_link(root, event.item.fspath)
                t = Title(belowchar='+')
                if link:
                    t.add(Link(itempath, link))
                else:
                    t.add(Text(itempath))
                if host:
                    t.add(Text('on %s' % (host.hostname,)))
                self.add_rest(t)
                if event.outcome.signal:
                    self.repr_signal(event.item, event.outcome)
                else:
                    self.repr_failure(event.item, event.outcome, tbstyle)
            else:
                itempath = self.get_path_from_item(event.item)
                root = self.get_rootpath(event.item)
                link = self.get_linkwriter().get_link(root, event.item.fspath)
                t = Title(abovechar='+', belowchar='+')
                if link:
                    t.add(Link(itempath, link))
                else:
                    t.add(Text(itempath))
                out = outcome.Outcome(excinfo=event.excinfo)
                self.repr_failure(event.item,
                                  outcome.ReprOutcome(out.make_repr()),
                                  tbstyle)

    def repr_signal(self, item, outcome):
        signal = outcome.signal
        self.add_rest(Title('Received signal: %d' % (outcome.signal,),
                            abovechar='+', belowchar='+'))
        if outcome.stdout.strip():
            self.add_rest(Paragraph('Captured process stdout:'))
            self.add_rest(LiteralBlock(outcome.stdout))
        if outcome.stderr.strip():
            self.add_rest(Paragraph('Captured process stderr:'))
            self.add_rest(LiteralBlock(outcome.stderr))

    def repr_failure(self, item, outcome, style):
        excinfo = outcome.excinfo
        traceback = excinfo.traceback
        if not traceback:
            self.add_rest(Paragraph('empty traceback from item %r' % (item,)))
            return
        self.repr_traceback(item, excinfo, traceback, style)
        if outcome.stdout:
            self.add_rest(Title('Captured process stdout:', abovechar='+',
                                belowchar='+'))
            self.add_rest(LiteralBlock(outcome.stdout))
        if outcome.stderr:
            self.add_rest(Title('Captured process stderr:', abovechar='+',
                                belowchar='+'))
            self.add_rest(LiteralBlock(outcome.stderr))

    def repr_traceback(self, item, excinfo, traceback, style):
        root = self.get_rootpath(item)
        self.add_rest(LinkTarget('traceback%d' % self.traceback_num, ""))
        self.traceback_num += 1
        if style == 'long':
            for entry in traceback:
                link = self.get_linkwriter().get_link(root,
                                            py.path.local(entry.path))
                if link:
                    self.add_rest(Title(Link(entry.path, link),
                                        'line %d' % (entry.lineno,),
                                        belowchar='+', abovechar='+'))
                else:
                    self.add_rest(Title('%s line %d' % (entry.path,
                                                        entry.lineno,),
                                        belowchar='+', abovechar='+'))
                self.add_rest(LiteralBlock(self.prepare_source(entry.relline,
                                                               entry.source)))
        elif style == 'short':
            text = []
            for entry in traceback:
                text.append('%s line %d' % (entry.path, entry.lineno))
                text.append('  %s' % (entry.source.strip(),))
            self.add_rest(LiteralBlock('\n'.join(text)))
        self.add_rest(Title(excinfo.typename, belowchar='+'))
        self.add_rest(LiteralBlock(excinfo.value))

    def prepare_source(self, relline, source):
        text = []
        for num, line in enumerate(source.split('\n')):
            if num == relline:
                text.append('>>> %s' % (line,))
            else:
                text.append('    %s' % (line,))
        return '\n'.join(text)

    def add_rest(self, item):
        self.rest.add(item)
        self.out.write('%s\n\n' % (item.text(),))

    def get_path_from_item(self, item):
        lns = item.listnames()[1:]
        for i, ln in enumerate(lns):
            if i > 0 and ln != '()':
                lns[i] = '/%s' % (ln,)
        itempath = ''.join(lns)
        return itempath

class AbstractLinkWriter(object):
    def get_link(self, base, path):
        pass

class NoLinkWriter(AbstractLinkWriter):
    def get_link(self, base, path):
        return ''

class LinkWriter(AbstractLinkWriter):
    def __init__(self, baseurl):
        self.baseurl = baseurl

    def get_link(self, base, path):
        relpath = path.relto(base)
        return self.baseurl + relpath

class RelLinkWriter(AbstractLinkWriter):
    def get_link(self, base, path):
        return path.relto(base)

