"""One hand-written schedule, run twice: without fencing, then with it.

The schedule below is an adversary. The pause boundaries are literal arguments
to `scenario()` -- A acquires a 10-tick lease at t=0 and is paused from t=2 to
t=16, so its lease dies at t=10 while A is not running to notice. B arrives at
t=12 and does everything right. Nothing else differs between the two runs:
`fencing` is a flag on the resource, not on the lock.

Then four sweeps, because "shorten the lease", "lengthen the lease" and "use
five servers like Redlock says" are the three fixes every reader proposes, and
all three can be measured instead of argued about.

  python3 demo.py
"""

from dlock import (scenario, overlapping_grants, stale_overwrites,
                   unlocked_writes)

TTL, PAUSE_FROM, PAUSE_TO, B_ARRIVES, N = 10, 2, 16, 12, 5
BAR = "=" * 68


def verdict(lock, res, st):
    """The two findings, printed side by side on purpose. The left-hand column
    is what happened to the data; the right-hand one is the lock's alibi."""
    stale = stale_overwrites(res)
    print(f"  final resource value: {res.value!r}")
    print(f"  stale overwrites:     "
          + (", ".join(f"t={b['t']} {b['client']} wrote with token "
                       f"{b['token']} over applied token {b['applied']}"
                       for b in stale) or "NONE"))
    print(f"  writes by a non-holder: "
          + (", ".join(f"t={w['t']} by {w['client']}"
                       for w in unlocked_writes(lock, res)) or "NONE"))
    print(f"  OVERLAPPING LEASES (any server, any pair): "
          f"{overlapping_grants(lock) or 'NONE'}")
    for s in lock.servers:
        for g in s.grants:
            print(f"    server {s.id}: {g['client']} token={g['token']} "
                  f"valid [{g['start']}, {g['end']})")


def run(fencing):
    lock, res, st, trace = scenario(ttl=TTL, pause_from=PAUSE_FROM,
                                    pause_to=PAUSE_TO, b_arrives=B_ARRIVES,
                                    n=N, fencing=fencing)
    print("\n".join(trace))
    verdict(lock, res, st)
    return lock, res, st


print(BAR)
print(f"  RUN 1 -- {N} lock servers, {TTL}-tick leases, NO fencing")
print(BAR)
run(False)

print()
print(BAR)
print("  RUN 2 -- byte-identical schedule, resource enforces fencing tokens")
print(BAR)
run(True)

print()
print(BAR)
print("  SWEEP 1 -- does adding lock servers fix it? (no fencing)")
print(BAR)
print("     N | quorum | final | stale overwrite | overlapping leases")
for n in (1, 3, 5, 7, 9, 51):
    lock, res, st, _ = scenario(ttl=TTL, pause_from=PAUSE_FROM,
                                pause_to=PAUSE_TO, b_arrives=B_ARRIVES, n=n)
    print(f"  {n:>4} | {lock.quorum:>6} | {res.value!r:>5} |"
          f" {str(bool(stale_overwrites(res))):>15} |"
          f" {len(overlapping_grants(lock)):>18}")

print()
print(BAR)
print("  SWEEP 2 -- does a different lease length fix it? (pause = 14 ticks)")
print(BAR)
print("    ttl | final | stale overwrite | B acquires at | B blocked for")
for ttl in (1, 2, 5, 8, 10, 12, 13, 14, 15, 16, 20, 40, 1000):
    lock, res, st, _ = scenario(ttl=ttl, pause_from=PAUSE_FROM,
                                pause_to=PAUSE_TO, b_arrives=B_ARRIVES, n=N)
    b_at = st.get("b_at")
    print(f"  {ttl:>5} | {res.value!r:>5} |"
          f" {str(bool(stale_overwrites(res))):>15} | {b_at:>13} |"
          f" {b_at - B_ARRIVES:>13}")

print()
print(BAR)
print("  SWEEP 3 -- the (ttl x pause) grid, no fencing")
print("             C = stale overwrite, . = clean")
print(BAR)
PAUSES = list(range(2, 42, 3))
TTLS = (1, 2, 4, 8, 10, 16, 25, 40, 80, 1000)


def grid(fencing):
    print("            pause: " + " ".join(f"{p:>3}" for p in PAUSES))
    for ttl in TTLS:
        row = []
        for pl in PAUSES:
            _, res, _, _ = scenario(ttl=ttl, pause_from=PAUSE_FROM,
                                    pause_to=PAUSE_FROM + pl,
                                    b_arrives=B_ARRIVES, n=N, fencing=fencing)
            row.append("  C" if stale_overwrites(res) else "  .")
        print(f"  ttl={ttl:>5}       " + " ".join(row))


grid(False)

print()
print(BAR)
print("  SWEEP 4 -- the same grid, with fencing")
print(BAR)
grid(True)
