"""One idle reader, 51 versions, and two garbage collectors that disagree.

The reader R touches exactly one key, `a`, and then sits there. Fifty writers
update a completely different key, `b`. R never reads `b`, never will, and
cannot be harmed by it -- yet R is the reason all 51 versions of `b` have to
stay in memory under the rule real databases actually use.
"""

from mvcc_store import HORIZON, PRECISE, Store

WRITERS = 50


def rule(store, name, kind, dry_run=False):
    before = store.total_versions()
    freed = store.vacuum(kind, dry_run=dry_run)
    after = before - freed
    verb = "would reclaim" if dry_run else "reclaims     "
    print(f"  {name:<24} {verb} {freed:>3}, leaving {after:>3}")


def main():
    store = Store()

    # --- T1 seeds both keys and commits. That is commit id 1. -------------
    t1 = store.begin()
    store.write(t1, "a", "A0")
    store.write(t1, "b", "B0")
    cid = store.commit(t1)
    print(f"T1 seeds a=A0, b=B0 and commits -> commit id {cid}")

    # --- R begins and reads only `a`. Then it goes idle, forever. ---------
    r = store.begin()
    print(f"R begins: snapshot = {r.snapshot}; R reads a -> {store.read(r, 'a')}")
    print("R now sits idle. It never touches b.")
    print()

    # --- 50 writers each update `b` and commit. ---------------------------
    for i in range(1, WRITERS + 1):
        w = store.begin()
        store.write(w, "b", f"B{i}")
        store.commit(w)
    print(f"{WRITERS} writers each update b and commit -> clock = {store.clock}")
    print(f"  versions of b: {len(store.chains['b'])}"
          f"   versions of a: {len(store.chains['a'])}"
          f"   total: {store.total_versions()}")

    # --- What each reader sees. ------------------------------------------
    fresh = store.begin()
    print(f"  R (snapshot {r.snapshot}) reads b as: {store.read(r, 'b')}"
          f"   <- a key it never asked for")
    print(f"  a fresh reader (snapshot {fresh.snapshot}) reads b as:"
          f" {store.read(fresh, 'b')}")

    # --- An uncommitted writer is invisible to everyone but itself. ------
    w = store.begin()
    store.write(w, "b", "B-uncommitted")
    print(f"  an uncommitted writer sets b=B-uncommitted;"
          f" it reads back {store.read(w, 'b')},"
          f" the fresh reader still sees {store.read(fresh, 'b')}")
    store.abort(w)
    print(f"  writer aborts; clock unchanged at {store.clock},"
          f" total versions still {store.total_versions()}")

    # --- The cost of the chain, paid on every read. -----------------------
    _, r_hops = store.probe(r, "b")
    _, fresh_hops = store.probe(fresh, "b")
    print(f"  chain hops to find b:  R {r_hops}   fresh reader {fresh_hops}")
    store.commit(fresh)
    print()

    # --- Two collectors, same 52 versions, same instant. ------------------
    print(f"R is still live, so the horizon is {store.horizon()}"
          f" and the live snapshots are {store.snapshots()}")
    rule(store, f"GC horizon={store.horizon()}", HORIZON)
    rule(store, "GC precise", PRECISE, dry_run=True)
    print()

    # --- Commit R, change nothing else, re-run the identical sweep. -------
    store.commit(r)
    print(f"R commits. Nothing else changes. Horizon is now {store.horizon()}.")
    rule(store, f"GC horizon={store.horizon()}", HORIZON)
    survivors = {k: v for k, v in store.chains.items()}
    print(f"  what survived: {survivors}")

    after = store.begin()
    _, after_hops = store.probe(after, "b")
    print(f"  chain hops to find b now: {after_hops}")


if __name__ == "__main__":
    main()
