#!/usr/bin/python3

import heapq
import time
import types
import logging

class EventLoop:
    def __init__(self):
        self.timers = []
        self.seq = 0
    def runAfter(self, delay, callback):
        self.seq += 1
        heapq.heappush(self.timers, (time.time() + delay, self.seq, callback))
    def poll(self):
        # here we can poll for sockets etc
        now = time.time()
        while self.timers and now >= self.timers[0][0]:
            heapq.heappop(self.timers)[2]()

loop = EventLoop()

class Future:
    def __init__(self):
        self.__hasResult = False
        self.__result = None
        self.__callback = None
    def done(self, result):
        if self.__callback:
            self.__callback(result)
        else:
            self.__result = result
            self.__hasResult = True
    def then(self, callback):
        if self.__hasResult:
            callback(self.__result)
        else:
            self.__callback = callback
    def hasResult(self):
        return self.__hasResult

class Spawn(Future):
    def __init__(self, gen):
        super().__init__()
        self.gen = gen
        self.step(None)
    def step(self, arg):
        try:
            res = self.gen.send(arg)
            if isinstance(res, types.GeneratorType):
                res = Spawn(res)
            res.then(self.step)
        except StopIteration as e:
            self.done(e.value)

class Sleep(Future):
    def __init__(self, delay):
        super().__init__()
        loop.runAfter(delay, lambda: self.done(None))

def foo(tid, delay, n):
    logging.debug("%d entered foo", tid)
    for i in range(0, n):
        logging.debug("%d before sleep", tid)
        yield Sleep(delay)
        logging.debug("%d after sleep", tid)
    logging.debug("%d exiting foo", tid)
    return n

def bar():
    logging.debug("entered bar")
    f1 = Spawn(foo(1, 0.1, 10))
    f2 = Spawn(foo(2, 0.15, 5))
    logging.debug("waiting for f1")
    r1 = yield f1
    logging.debug("f1 result is %d", r1)
    logging.debug("waiting for f2")
    r2 = yield f2
    logging.debug("f1 result is %d", r2)
    logging.debug("exiting bar")

logging.basicConfig(format="%(asctime)-15s %(message)s", level=logging.DEBUG)

s = Spawn(bar())
while not s.hasResult():
    loop.poll()