"""The settings, words and chart numbers of the widget: lesson 31 (build-a-tiny-model-server).

`describe` runs the tiny server at one setting and returns everything the widget shows. The widget (../widget.html) repeats `serve`, `report` and
`describe` in JavaScript, and test_widget.py runs both and compares them. Standard library only.
"""
from tiny_server import HEAVY, LIMIT, N_CHATS, PAGES, WORKLOAD, bottleneck, report, serve, tail

RATES, PAGE_STOPS, LIMIT_STOPS = [10, 20, 30, 40], [120, 240, 480, 960], [8, 16, 24, 48]     # the three sliders' stops
OPENING = {"rate": HEAVY, "pages": PAGES, "limit": LIMIT}                                       # the widget opens on the lesson's heavy run
REROLLS = tail.draw_rerolls(count=6, n=N_CHATS)                                                 # "Shuffle the chats": same chats, another order
COLUMNS = 48                                                                                    # slices of the run the chart shows
WALL_MOVES = [(240, 16), (480, 16), (960, 16), (480, 24)]                                       # the settings the lesson walks through, at the heavy rate
WALLS = {"none": "none", "cache": "the cache", "batch": "the batch limit", "both": "both"}

seconds_text = tail.seconds_text


def ms_text(seconds: float) -> str:
    return f"{seconds * 1000:.1f}"


def chats_text(n: int) -> str:
    return f"{n} chat" if n == 1 else f"{n} chats"


def columns(run, pages: int) -> dict:
    """The chart: for each slice of the run, the most pages promised (held plus owed) and the longest queue, and the slice where the cache first filled."""
    last = run.steps[-1]
    end = last.start + last.seconds
    promised, queue, full = [0] * COLUMNS, [0] * COLUMNS, -1
    for s in run.steps:
        c = min(COLUMNS - 1, int(s.start * COLUMNS / end))
        promised[c] = max(promised[c], s.held + s.owed)
        queue[c] = max(queue[c], s.queue)
        if full < 0 and "cache" in s.blocked:
            full = c
    return {"end": end, "promised": promised, "queue": queue, "full": full}


def headline(m: dict, rate: int, limit: int) -> str:
    slow = f"1\u00a0chat in 100 waits more than {seconds_text(m['ttft_p99'])}\u00a0s for its first word."
    wall = bottleneck(m)
    if wall == "none":
        return f"At {rate}\u00a0chats a second no wall binds. {slow}"
    if wall == "cache":
        return f"At {rate}\u00a0chats a second the cache fills at {seconds_text(m['cache_full_at'])}\u00a0s and new chats wait. {slow}"
    if wall == "batch":
        return f"At {rate}\u00a0chats a second new chats mostly wait for a place in the batch, which holds {limit}. {slow}"
    return f"At {rate}\u00a0chats a second new chats wait for cache pages and for a place in the batch. {slow}"


def key_table(m: dict) -> list:
    return [["First word", seconds_text(m["ttft_p50"]) + " s", seconds_text(m["ttft_p99"]) + " s"],
            ["Between words", ms_text(m["tbt_p50"]) + " ms", ms_text(m["tbt_p99"]) + " ms"]]


def key_lines(m: dict, pages: int) -> list:
    return [f"Longest queue: {chats_text(m['queue_max'])}", f"Cache pages used, most: {m['pages_max']} of {pages}", f"Wall: {WALLS[bottleneck(m)]}"]


def describe(rate: int, pages: int, limit: int, reroll: int = 0) -> dict:
    a, b = REROLLS[reroll]
    workload = tail.shuffled(WORKLOAD, a, b)
    run = serve(workload, rate, pages, limit)
    m = report(workload, rate, run)
    return {"head": headline(m, rate, limit), "table": key_table(m), "lines": key_lines(m, pages), "chart": columns(run, pages),
            "metrics": {k: m[k] for k in ("ttft_p50", "ttft_p99", "tbt_p50", "tbt_p99", "queue_max", "pages_max", "cache_full_at", "served", "cache_steps", "batch_steps")}}
