'''
This file contains some utility classes which are used by both the client and
server components of the port tester application.
'''

import time
import Axon
from Axon.Ipc import producerFinished, shutdownMicroprocess, shutdown
from Kamaelia.IPC import serverShutdown

class SingleTick(Axon.ThreadedComponent.threadedcomponent):
    '''
    This threaded component will wait "delay" seconds then send True out it's
    outbox and shutdown.  You can specify an optional "check_interval" which
    will cause the component to periodically check it's control inbox for
    early termination signals.
    
    SingleTick(delay, check_interval=delay, tick_mesg=True)
    '''
    Inboxes = {'inbox':'ignored', 
               'control':'Sending a message here will cause the component to shutdown'}
    Outboxes= {'outbox':'Sends "tick_mesg" (def: True) after "delay" seconds unless interrupted first', 
               'signal':'Sends producerFinished if not interrupted else sends interruption message'}
    tick_mesg = True
    check_interval = None
    
    def __init__(self, delay, **kwargs):
        super(SingleTick, self).__init__(**kwargs)
        self.delay = delay
        if self.check_interval is None or self.check_interval > delay:
            self.check_interval = delay

    def main(self):
        delay_until = time.time() + self.delay
        remaining = self.delay
        while remaining > 0 and not self.dataReady('control'):
            if remaining < self.check_interval:
                self.pause(remaining)
            else:
                self.pause(self.check_interval)
            remaining = delay_until - time.time()

        if remaining <= 0:
            self.send(self.tick_mesg, 'outbox')
            self.send(producerFinished(self), 'signal')
        elif self.dataReady('control'):
            self.send(self.recv('control'), 'signal')


class PeriodicTick(Axon.ThreadedComponent.threadedcomponent):
    '''
    This threaded component will periodically (every "delay" seconds) send
    True out it's outbox.  You can specify an optional "check_interval" which
    will cause the component to more frequently check it's control inbox for
    termination signals.
    
    PeriodicTick(delay, check_interval=delay, tick_mesg=True)
    '''
    Inboxes = {'inbox':'ignored', 
               'control':'Sending a message here will cause the component to shutdown'}
    Outboxes= {'outbox':'Sends a "tick_mesg" (def: True) every "delay" seconds', 
               'signal':'Sends terminiation signal received on "control"'}
    tick_mesg = True
    check_interval = None
    
    def __init__(self, delay, **kwargs):
        super(PeriodicTick, self).__init__(**kwargs)
        self.delay = delay
        if self.check_interval is None or self.check_interval > delay:
            self.check_interval = delay

    def main(self):
        start_time = time.time()
        tick_count = 1
        while not self.dataReady('control'):
            delay_until = start_time + self.delay * tick_count
            remaining = delay_until - time.time()
            while remaining > 0 and not self.dataReady('control'):
                if remaining < self.check_interval:
                    self.pause(remaining)
                else:
                    self.pause(self.check_interval)
                remaining = delay_until - time.time()

            if remaining <= 0:
                self.send(self.tick_mesg, 'outbox')
                tick_count += 1
        self.send(self.recv('control'), 'signal')


class TTL(Axon.Component.component):
    '''
    This "Time To Live" component is designed to wrap another existing component.
    The TTL starts an embedded SingleTick component which waits for "delay"
    seconds and then the TTL progressivly becomes more aggressive in its attempts
    to shutdown the wrapped component.  Ideally this component should not be
    needed, but it is handy for components that do not have their own timeout
    functionality.
    
    TTL(comp, delay)
    '''
    Inboxes = {'_trigger':'Receives True message to cause embedded component to shutdown'}
    Outboxes= {'_sigkill':'Dynamically links to a emedded component control',
               '_disarm':'Stop timebomb early'}
    
    def __init__(self, comp, delay):
        # One of the rare cases where we do not call the parent class' init()
        # right off the bat.  Instead we first replicate the wrapped component's
        # inboxes and outboxes.  Private "_name" boxes are not replicated.
        self.child = comp
        for inbox in (item for item in self.child.Inboxes.iteritems() if not item[0].startswith('_')):
            self.Inboxes[inbox[0]] = inbox[1]
        for outbox in (item for item in self.child.Outboxes.iteritems() if not item[0].startswith('_')):
            self.Outboxes[outbox[0]] = outbox[1]

        super(TTL, self).__init__()

        self.timebomb = SingleTick(delay=delay, check_interval=1)

        # We can now create the mailbox linkages now that the parent class'
        # init() has been called.
        self.link((self.timebomb, 'outbox'), (self, '_trigger'))
        self.link((self, '_disarm'), (self.timebomb, 'control'))
        self.link((self, '_sigkill'), (self.child, 'control'))

        for inbox in (item for item in self.child.Inboxes.iteritems() if not item[0].startswith('_')):
            self.link((self, inbox[0]), (self.child, inbox[0]), passthrough=1)
 
        for outbox in (item for item in self.child.Outboxes.iteritems() if not item[0].startswith('_')):
            self.link((self.child, outbox[0]), (self, outbox[0]), passthrough=2)
        
        self.addChildren(self.child)
    
    def main(self):
        self.timebomb.activate()
        self.child.activate()
        yield 1
        while not (self.child._isStopped() or (self.dataReady('_trigger') and self.recv('_trigger') is True)):
            self.pause()
            yield 1
        if not self.timebomb._isStopped():
            self.send(producerFinished(), '_disarm')
        if not self.child._isStopped():
            self.send(producerFinished(), '_sigkill')
            yield 1
            yield 1
            if not self.child._isStopped():
                self.send(shutdownMicroprocess(), '_sigkill')
                yield 1
                yield 1
                if not self.child._isStopped():
                    self.send(serverShutdown(), '_sigkill')
                    yield 1
                    yield 1
                    if not self.child._isStopped():
                        self.send(shutdown(), '_sigkill')
                        yield 1
                        yield 1
        self.removeChild(self.child)
        yield 1
        if not self.child._isStopped():
            self.child.stop()
            yield 1
            if 'signal' in self.Outboxes:
                self.send(shutdownMicroprocess(), 'signal')
                yield 1


class DataSink(Axon.Component.component):
    '''
    This is a limited use component.  It is sort of the inverse of the
    DataSource component.  It takes messages in the inbox and sticks them
    in a list and passes them on to its outbox.  The list is not thread-
    safe, so do not access it until the component has shutdown.
    
    DataSink(outlist, limit=0, pass_thru=True)
    '''
    Inboxes = {'inbox':'Receives data to be stored in list-like variable', 
               'control':'Sending a message here will cause the component to shutdown'}
    Outboxes = {'outbox':'Optionally passes through copy of data received on inbox',
                'signal':'Sends producerFinished if limit reached otherwise passes termination signal received on "control"'}
    limit = 0
    pass_thru = True

    def __init__(self, outlist, **kwargs):
        super(DataSink, self).__init__(**kwargs)
        self.outlist = outlist

    def main(self):
        count = 0
        while not self.dataReady('control') and (self.limit == 0 or count < self.limit):
            while not (self.dataReady('inbox') or self.dataReady('control')):
                self.pause()
                yield 1
            if self.dataReady('inbox'):
                data = self.recv('inbox')
                self.outlist.append(data)
                if self.pass_thru:
                    self.send(data, 'outbox')
                count += 1

        if self.dataReady('control'):
            self.send(self.recv('control'), 'signal')
        else:
            self.send(producerFinished(self), 'signal')
        yield 1
