"""The demo: a punctual timer delivered late, and the arithmetic behind it.

Every number printed here comes out of mini_asyncio.py on a virtual clock,
so this file prints the same bytes on every machine and every run.
"""

import math

from mini_asyncio import Loop, burn, sleep, deliveries, periods

PERIOD = 1000      # the heartbeat's requested period, in virtual ms
CHUNK = 200        # CPU the neighbour burns between two yield points
HORIZON = 8000     # how long to run


def heartbeat(period):
    """A well-behaved task: asks for a fixed period and does no work."""
    while True:
        yield sleep(period)


def hog(chunk):
    """A neighbour that burns `chunk` ms of CPU between yield points.

    It is not badly written: it *does* yield, every single iteration. It
    simply does a chunk of ordinary Python before it does.
    """
    while True:
        yield burn(chunk)


def run(hops, chunk, period=PERIOD, n_hogs=1, until=HORIZON):
    loop = Loop(hops=hops)
    loop.spawn("beat", heartbeat(period))
    for i in range(n_hogs):
        loop.spawn(f"hog{i}", hog(chunk))
    loop.run(until)
    return loop


def rule(title):
    print()
    print(title)
    print("-" * len(title))


print("mini-asyncio: a timer is a floor, not a promise")
print(f"heartbeat asks for sleep({PERIOD}ms); neighbour burns {CHUNK}ms "
      f"between yields")

rule("1. The heartbeat alone")
loop = Loop(hops=2)
loop.spawn("beat", heartbeat(PERIOD))
loop.run(HORIZON)
print(f"periods: {periods(loop, 'beat')}")
print("nothing else wants the CPU, so the loop idles forward to each timer")
print("and every delivery is exact.")

rule("2. The heartbeat next to one hog (hops=2, faithful to asyncio.sleep)")
loop = Loop(hops=2)
loop.spawn("beat", heartbeat(PERIOD))
loop.spawn("hog0", hog(CHUNK))
loop.run(HORIZON)
print(f"{'asked for':>9} {'expired':>8} {'late':>5} {'callback':>9} "
      f"{'late':>5} {'task ran':>9} {'late':>5}")
for w in deliveries(loop, "beat"):
    print(f"{w.deadline:9d} {w.expired_at:8d} {w.expired_at - w.deadline:5d} "
          f"{w.fired_at:9d} {w.fired_at - w.deadline:5d} "
          f"{w.ran_at:9d} {w.ran_at - w.deadline:5d}")
print(f"periods: {periods(loop, 'beat')}")
print()
print("Read the 'expired' column: the loop noticed every one of these timers")
print("at the exact millisecond it came due. Not once late, not by 1ms. A")
print("loop instrumenting its own timer heap would report a perfectly")
print("healthy scheduler. The task still resumed 400ms after it asked to,")
print("and nothing anywhere raised, warned, or returned an error.")

rule("3. The same run with hops=1 (faithful to loop.call_later)")
loop1 = run(1, CHUNK)
print(f"periods: {periods(loop1, 'beat')}")
print()
print("Same loop, same hog, same 1000ms request. The only difference is that")
print("the timer's callback resumes the task itself instead of resolving a")
print("future that then schedules the task. That indirection is worth 200ms")
print("-- one whole trip through the ready queue -- on every single beat.")

rule("4. Where the number comes from")
print("R = one full pass of the ready queue = n_hogs * chunk")
print("observed = (ceil(PERIOD / R) + hops) * R")
print()
print(f"{'hogs':>5} {'chunk':>6} {'hops':>5} {'R':>5} {'ceil(P/R)':>10} "
      f"{'predicted':>10} {'observed':>9}")
for n_hogs in (1, 2, 3):
    for chunk in (100, 200, 300, 400):
        for hops in (1, 2):
            R = n_hogs * chunk
            pred = (math.ceil(PERIOD / R) + hops) * R
            obs = set(periods(run(hops, chunk, n_hogs=n_hogs, until=40000),
                              "beat"))
            print(f"{n_hogs:5d} {chunk:6d} {hops:5d} {R:5d} "
                  f"{math.ceil(PERIOD / R):10d} {pred:10d} "
                  f"{str(sorted(obs)):>9}")

rule("5. The only lever is R")
print("`await sleep(0)` is what makes a burn a yield point at all. It cannot")
print("push lateness below hops * R. Shrinking the chunk is the whole fix:")
print()
print(f"{'chunk':>6} {'period':>8} {'late':>6} {'hops*R':>7}")
for chunk in (200, 100, 50, 10, 1):
    obs = periods(run(2, chunk, until=20000), "beat")[0]
    print(f"{chunk:6d} {obs:8d} {obs - PERIOD:6d} {2 * chunk:7d}")
print()
print("The chunk you may hold is set by the tightest timer anywhere else in")
print("the process -- a number your own module does not know.")
