"""Print every number the lesson quotes. Run: python3 show_numbers.py"""
import statistics

import show
import tiny_server as ts

W = ts.WORKLOAD
ARRIVE = ts.intended_times(W["gaps"], ts.HEAVY)


def runs() -> dict:
    """The settings the lesson walks through, each as (run, metrics)."""
    out = {}
    for name, rate, pages, limit in (("light", ts.LIGHT, ts.PAGES, ts.LIMIT), ("heavy", ts.HEAVY, ts.PAGES, ts.LIMIT), ("480 pages", ts.HEAVY, 480, ts.LIMIT),
                                     ("960 pages", ts.HEAVY, 960, ts.LIMIT), ("480 pages, limit 24", ts.HEAVY, 480, 24)):
        run = ts.serve(W, rate, pages, limit)
        out[name] = (run, ts.report(W, rate, run))
    return out


def heavy_facts(run) -> dict:
    """What the heavy run does while the cache is full: the queue at set times, and how many chats waited."""
    first_full = next(k for k, s in enumerate(run.steps) if "cache" in s.blocked)
    at = lambda t: next(s for s in reversed(run.steps) if s.start <= t).queue
    waits = [f - a for f, a in zip(run.first, ARRIVE)]
    return {"full_step": first_full + 1, "full": run.steps[first_full], "queue_at": {t: at(t) for t in (0.5, 1, 2, 3, 5, 10)},
            "over_1s": sum(1 for w in waits if w > 1), "longest": max(waits), "arrive_end": ARRIVE[-1], "end": run.steps[-1].start + run.steps[-1].seconds,
            "steps": len(run.steps), "full_steps_first_3s": sum(1 for s in run.steps if s.start < 3 and "cache" in s.blocked), "steps_first_3s": sum(1 for s in run.steps if s.start < 3)}


def main() -> None:
    pages = [ts.pages_for(p + r) for p, r in zip(W["prompts"], W["replies"])]
    print(f"workload: {ts.N_CHATS} chats from lesson 27's seed; prompts {min(W['prompts'])} to {max(W['prompts'])} tokens; replies {min(W['replies'])} to {max(W['replies'])}; "
          f"whole chats {min(p + r for p, r in zip(W['prompts'], W['replies']))} to {max(p + r for p, r in zip(W['prompts'], W['replies']))} tokens, mean {statistics.fmean(p + r for p, r in zip(W['prompts'], W['replies'])):.1f}")
    print(f"pages per chat: mean {statistics.fmean(pages):.2f}, most {max(pages)}; the cache: {ts.PAGES} pages = {ts.PAGES * 2} MiB (a page is 16 tokens x 128 KiB = 2 MiB); a real card holds 25,720")
    print(f"step time with 1 chat and a 34-token prompt: {ts.step_seconds([], 34) * 1000:.2f} ms; with 16 chats of 220 tokens: {ts.step_seconds([220] * 16, 0) * 1000:.2f} ms")
    print(f"the 400 chats arrive over {ARRIVE[-1]:.1f} s at {ts.HEAVY} a second and over {ts.intended_times(W['gaps'], ts.LIGHT)[-1]:.1f} s at {ts.LIGHT}")
    print()
    for name, (run, m) in runs().items():
        print(f"{name:20} wall {ts.bottleneck(m):6} first word p50 {m['ttft_p50']:.3f} p99 {m['ttft_p99']:.3f} s | between words p50 {m['tbt_p50'] * 1000:.1f} p99 {m['tbt_p99'] * 1000:.1f} ms | "
              f"queue {m['queue_max']} pages {m['pages_max']} | full at {m['cache_full_at']} | cache steps {m['cache_steps']} batch steps {m['batch_steps']} | served {m['served']:.1f} a second | ends {run.steps[-1].start + run.steps[-1].seconds:.2f} s, {len(run.steps)} steps")
    print()
    print("the two kinds of TBT (ms): every gap pooled (the toy) / one average per chat (p50, p99 of 400), and the steps behind the gaps")
    for name, (run, m) in runs().items():
        per_chat = [(d - f) / (r - 1) for d, f, r in zip(run.done, run.first, W["replies"])]
        alone, reading = [s.seconds for s in run.steps if not s.joined], [s.seconds for s in run.steps if s.joined]
        print(f"{name:20} per gap {m['tbt_p50'] * 1000:.1f} / {m['tbt_p99'] * 1000:.1f} | per chat {ts.percentile(per_chat, 50) * 1000:.1f} / {ts.percentile(per_chat, 99) * 1000:.1f} | "
              f"steps with no new chat {len(alone)}, mean {statistics.fmean(alone) * 1000:.2f}, longest {max(alone) * 1000:.2f} | steps that read prompts {len(reading)}, "
              f"mean {statistics.fmean(reading) * 1000:.2f}, longest {max(reading) * 1000:.2f}")
    print()
    print("heavy run:", heavy_facts(runs()["heavy"][0]))
    print()
    print("widget shuffles at 40 chats a second (p99 first word, wall):")
    for pages_, limit in [(ts.PAGES, ts.LIMIT), *show.WALL_MOVES[1:]]:
        ds = [show.describe(ts.HEAVY, pages_, limit, k) for k in range(len(show.REROLLS))]
        print(f"  {pages_} pages, limit {limit}: p99 {min(d['metrics']['ttft_p99'] for d in ds):.2f} to {max(d['metrics']['ttft_p99'] for d in ds):.2f} s; walls", [d["lines"][2] for d in ds])


if __name__ == "__main__":
    main()
