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.
    '''
    
    def __init__(self, delay, check_interval=None):
        super(SingleTick, self).__init__()
        if check_interval is None or check_interval > delay:
            self.check_interval = delay
        else:
            self.check_interval = check_interval
        self.delay = delay

    def main(self):
        delay_until = time.time() + self.delay
        self.pause(self.check_interval)
        now = time.time()
        while now < delay_until:
            if self.dataReady('control'):
                self.send(self.recv('control'), 'signal')
                break
            else:
                remainder = delay_until - now
                if remainder < self.check_interval and remainder > 0:
                    self.pause(remainder)
                else:
                    self.pause(self.check_interval)
            now = time.time()
        else:
            self.send(True, 'outbox')
            self.send(producerFinished(self), '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.
    '''
    
    def __init__(self, delay, check_interval=None):
        super(PeriodicTick, self).__init__()
        if check_interval is None or check_interval > delay:
            self.check_interval = delay
        else:
            self.check_interval = check_interval
        self.delay = delay

    def main(self):
        start_time = time.time()
        tick_count = 1
        while not self.dataReady('control'):
            delay_until = start_time + self.delay * tick_count
            self.pause(self.check_interval)
            now = time.time()
            while now < delay_until:
                if self.dataReady('control'):
                    break
                else:
                    remainder = delay_until - now
                    if remainder < self.check_interval and remainder > 0:
                        self.pause(remainder)
                    else:
                        self.pause(self.check_interval)
                now = time.time()
            else:
                self.send(True, '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.
    '''

    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.
    '''
    
    Inboxes = {'inbox':'Receives data to be stored in variables', 
               'control':'Shutdown signaling'}
    Outboxes = {'outbox':'Optionally passes through copy of data received on inbox',
                'signal':'Shutdown signaling'}

    def __init__(self, outlist, limit=0, passthrough=True):
        super(DataSink, self).__init__()
        self.outlist = outlist
        self.limit = limit
        self.passthrough = passthrough

    def main(self):
        count = 0
        while 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.passthrough:
                    self.send(data, 'outbox')
                count += 1
            if self.dataReady('control'):
                self.send(self.recv('control'), 'signal')
                break
        else:
            self.send(producerFinished(self), 'signal')
        yield 1

