"""
    LooponfailingSession and Helpers. 

    NOTE that one really has to avoid loading and depending on 
    application modules within the controlling process 
    (the one that starts repeatedly test processes)
    otherwise changes to source code can crash 
    the controlling process which should never happen. 
"""
import py
import sys
import execnet
from py._test.session import Session
from xdist import util

class LooponfailingSession(Session):
    def __init__(self, config):
        super(LooponfailingSession, self).__init__(config=config)
        self.rootdirs = [self.config.topdir] # xxx dist_rsync_roots? 
        self.statrecorder = util.StatRecorder(self.rootdirs) 
        self.remotecontrol = RemoteControl(self.config)
        self.out = py.io.TerminalWriter()

    def main(self, initialitems):
        try:
            self.loopstate = loopstate = LoopState([])
            while 1:
                self.loop_once(loopstate)
                if not loopstate.colitems and loopstate.wasfailing:
                    continue # the last failures passed, let's rerun all
                self.statrecorder.waitonchange(checkinterval=2.0) 
        except KeyboardInterrupt:
            print

    def loop_once(self, loopstate):
        self.remotecontrol.setup()
        colitems = loopstate.colitems
        loopstate.wasfailing = colitems and len(colitems)
        loopstate.colitems = self.remotecontrol.runsession(colitems or ())

class LoopState:
    def __init__(self, colitems=None):
        self.colitems = colitems

class RemoteControl(object):
    def __init__(self, config):
        self.config = config

    def trace(self, *args):
        if self.config.option.debug:
            msg = " ".join([str(x) for x in args])
            py.builtin.print_("RemoteControl:", msg)

    def initgateway(self):
        return execnet.makegateway("popen")

    def setup(self, out=None):
        if out is None:
            out = py.io.TerminalWriter()
        if hasattr(self, 'gateway'):
            raise ValueError("already have gateway %r" % self.gateway)
        self.trace("setting up slave session")
        self.gateway = self.initgateway()
        self.channel = channel = self.gateway.remote_exec("""
            import os, sys
            import py
            chdir = channel.receive()
            outchannel = channel.gateway.newchannel()
            channel.send(outchannel)
            # prune sys.path to not contain relative paths
            newpaths = []
            for p in sys.path:
                if p:
                    if not os.path.isabs(p):
                        p = os.path.abspath(p)
                    newpaths.append(p)
            sys.path[:] = newpaths     
            os.chdir(chdir) # unpickling config uses cwd as topdir
            
            config_state = channel.receive()
            fullwidth, hasmarkup = channel.receive()
            py.test.config.__setstate__(config_state)

            import sys
            sys.stdout = sys.stderr = outchannel.makefile('w')

            from xdist.remote import slave_runsession
            slave_runsession(channel, py.test.config, fullwidth, hasmarkup) 
        """)
        channel.send(str(self.config.topdir))
        remote_outchannel = channel.receive()
        def write(s):
            out._file.write(s)
            out._file.flush()
        remote_outchannel.setcallback(write)
        channel.send(self.config.__getstate__())
        channel.send((out.fullwidth, out.hasmarkup))
        self.trace("set up of slave session complete")

    def ensure_teardown(self):
        if hasattr(self, 'channel'):
            if not self.channel.isclosed():
                self.trace("closing", self.channel)
                self.channel.close()
            del self.channel
        if hasattr(self, 'gateway'):
            self.trace("exiting", self.gateway)
            self.gateway.exit()
            del self.gateway

    def runsession(self, colitems=()):
        try:
            self.trace("sending", colitems)
            trails = colitems
            self.channel.send(trails)
            try:
                return self.channel.receive()
            except self.channel.RemoteError:
                e = sys.exc_info()[1]
                self.trace("ERROR", e)
                raise
        finally:
            self.ensure_teardown()

def slave_runsession(channel, config, fullwidth, hasmarkup):
    """ we run this on the other side. """
    if config.option.debug:
        def DEBUG(*args): 
            print(" ".join(map(str, args)))
    else:
        def DEBUG(*args): pass

    DEBUG("SLAVE: received configuration, using topdir:", config.topdir)
    #config.option.session = None
    config.option.looponfail = False 
    config.option.usepdb = False 
    try:
        trails = channel.receive()
    except KeyboardInterrupt:
        return # in the slave we can't do much about this
    config.pluginmanager.do_configure(config)
    DEBUG("SLAVE: initsession()")
    session = config.initsession()
    # XXX configure the reporter object's terminal writer more directly
    # XXX and write a test for this remote-terminal setting logic 
    config.pytest_terminal_hasmarkup = hasmarkup
    config.pytest_terminal_fullwidth = fullwidth
    if trails:
        colitems = []
        for trail in trails:
            try:
                colitem = config._rootcol.fromtrail(trail)
            except ValueError:
                #XXX send info for "test disappeared" or so
                continue 
            colitems.append(colitem)
    else:
        colitems = config.getinitialnodes()
    session.shouldclose = channel.isclosed 
   
    class Failures(list):
        def pytest_runtest_logreport(self, report):
            if report.failed:
                self.append(report)
        pytest_collectreport = pytest_runtest_logreport
        
    failreports = Failures()
    session.pluginmanager.register(failreports)

    DEBUG("SLAVE: starting session.main()")
    session.main(colitems)
    session.config.hook.pytest_looponfailinfo(
        failreports=list(failreports), 
        rootdirs=[config.topdir])
    rootcol = session.config._rootcol
    channel.send([rootcol.totrail(rep.getnode()) for rep in failreports])
