"""What the widget (../widget.html) shows, worked out from pd_sim.py. Run it from this folder: python3 show.py

The widget cannot run Python, so this file runs the toy once for every setting its controls offer, keeps how many of the 600 requests were good and the slowest 1 in 100 times,
and embed_widget.py writes those numbers into widget.html. The words the page shows are built here and again in the page; test_widget.py checks the two agree for every setting.
A control is an index into one of the tuples below (the first stop is 0).
"""
from __future__ import annotations

from functools import cache

import pd_sim as sim

RPS = (2, 4, 10)                            # requests a second
PROMPTS = (200, 1000, 2000)                 # tokens in a typical prompt
REPLIES = (100, 200, 400)                   # words in a typical reply
LINKS = (0.25, 0.5, 1.25, 2.5, 12.5, 25)    # GB/s, 10**9 bytes per second (2, 4, 10, 20, 100 and 200 gigabits per second)
FIRST_TARGETS = (0.25, 0.5, 1.0)            # seconds to the first word
GAP_TARGETS = (0.05, 0.1, 0.2)              # seconds between two words
OPENING = {"rps": 1, "prompt": 2, "link": 5, "reply": 1, "first": 1, "gap": 1}    # 4 a second, 2000 tokens, 25 GB/s, 200 words, 0.5 s, 0.1 s: the toy's default
N = sim.N_REQUESTS


def summarize(requests: list[sim.Request], run: tuple[dict, dict]) -> dict:
    """For one layout: the requests that were good under each pair of targets (first word target varies slowest), then the slowest 1 in 100 first word and gap, in ms."""
    first, gaps = run
    waits = [first[i] - r.arrival for i, r in enumerate(requests)]
    longest = [max(gaps[i], default=0.0) for i in range(len(requests))]
    pooled = [g for i in range(len(requests)) for g in gaps[i]]
    return {"good": [sum(1 for w, g in zip(waits, longest) if w <= f and g <= t) for f in FIRST_TARGETS for t in GAP_TARGETS],
            "first": round(sim.percentile(waits, 99) * 1000, 1), "gap": round(sim.percentile(pooled, 99) * 1000, 1)}


@cache
def table() -> list:
    """cells[rps][prompt][reply] = {"shared": summary, "split": [summary for each link speed]}."""
    cells = []
    for rps in RPS:
        row = []
        for prompt in PROMPTS:
            column = []
            for reply in REPLIES:
                requests = sim.make_requests(rps, prompt, reply)
                column.append({"shared": summarize(requests, sim.shared(requests)), "split": [summarize(requests, sim.split(requests, link)) for link in LINKS]})
            row.append(column)
        cells.append(row)
    return cells


def widget_config() -> dict:
    """The block of numbers embedded in widget.html."""
    return {"n": N, "rps": RPS, "prompt": PROMPTS, "reply": REPLIES, "link": LINKS, "first": FIRST_TARGETS, "gap": GAP_TARGETS, "opening": OPENING, "cells": table()}


def describe(rps: int, prompt: int, link: int, reply: int, first: int, gap: int) -> dict:
    """The words for one setting and the counts the chart draws: the shared layout's good requests, and the split's at every link speed."""
    cell, k = table()[rps][prompt][reply], first * len(GAP_TARGETS) + gap
    a, b, pct = cell["shared"], cell["split"][link], lambda count: f"{count / N * 100:.1f}%"
    return {"head": f"Both layouts get {pct(a['good'][k])} of requests right." if a["good"][k] == b["good"][k]
            else f"Shared GPUs get {pct(a['good'][k])} of requests right and the split gets {pct(b['good'][k])}.",
            "rule": f"Right means the first word within {FIRST_TARGETS[first]:g} s and no gap between words over {GAP_TARGETS[gap]:g} s.",
            "tails": f"Slowest 1 in 100: first word {a['first']:.1f} ms shared, {b['first']:.1f} ms split; gap between words {a['gap']:.1f} ms shared, {b['gap']:.1f} ms split.",
            "shared": a["good"][k], "split": [s["good"][k] for s in cell["split"]]}


if __name__ == "__main__":
    shown = describe(*(OPENING[name] for name in ("rps", "prompt", "link", "reply", "first", "gap")))
    print(shown["head"], shown["rule"], shown["tails"], sep="\n")
