"""Exact arithmetic for a priority crossing, not a cache or workload simulator."""
from fractions import Fraction
from math import comb
import json


def integer(value, name, minimum=0):
    if type(value) is not int or value < minimum:
        raise ValueError(f"{name} must be an integer >= {minimum}")
    return value


def score(count, inserted, now):
    integer(count, "count", 1)
    integer(inserted, "inserted")
    integer(now, "now")
    if now <= inserted:
        raise ValueError("priority requires strictly positive residence age")
    return Fraction(count, now - inserted)


def crossing(count_a, inserted_a, count_b, inserted_b):
    for count in (count_a, count_b):
        integer(count, "count", 1)
    for inserted in (inserted_a, inserted_b):
        integer(inserted, "inserted")
    if count_a == count_b:
        return None
    time = Fraction(count_a * inserted_b - count_b * inserted_a, count_a - count_b)
    return time if time > max(inserted_a, inserted_b) else None


def sample_probability(population, qualifying, draws, *, replacement):
    integer(population, "population", 1)
    integer(qualifying, "qualifying")
    integer(draws, "draws")
    if type(replacement) is not bool or qualifying > population:
        raise ValueError("invalid sampling population or replacement flag")
    if not replacement and draws > population:
        raise ValueError("without-replacement sample exceeds population")
    if replacement:
        return 1 - Fraction(population - qualifying, population) ** draws
    return 1 - Fraction(comb(population - qualifying, draws), comb(population, draws))


def example():
    counts, inserted = {"A": 4, "B": 1}, {"A": 0, "B": 9}
    rows = []
    for now in (10, 12, 20):
        scores = {key: score(counts[key], inserted[key], now) for key in counts}
        rows.append({"time": now, "scores": {key: str(value) for key, value in scores.items()},
                     "lowest_priority": [key for key, value in scores.items() if value == min(scores.values())]})
    return {
        "schema_version": 1,
        "kind": "constructed exact arithmetic; not a measured workload",
        "clock_unit": "arbitrary common elapsed-time units",
        "counts": counts, "inserted_at": inserted,
        "assumptions": "Both items stay resident; no later accesses to A or B, expiration, costs or evictions.",
        "crossing_time": str(crossing(4, 0, 1, 9)),
        "rows": rows,
        "sampling": {"population": 100, "qualifying": 10, "draws": 8,
                     "event": "at least one sampled item belongs to the fixed lowest-priority ten",
                     "with_replacement": str(sample_probability(100, 10, 8, replacement=True)),
                     "without_replacement": str(sample_probability(100, 10, 8, replacement=False))},
        "limits": "No cache hit-rate, throughput, victim-sequence or paper-result replication claim.",
    }


if __name__ == "__main__":
    print(json.dumps(example(), indent=2))
