#!/usr/bin/env python3
"""Plot bits/key vs. false positive rate from results.txt (output of ./filters).

Usage: /tmp/mplenv/bin/python plot_space_fpr.py   (writes ../space-fpr.svg)
Dashed curves are formulas; markers are measurements from filters.c.
"""
import math
import os
import re

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

HERE = os.path.dirname(os.path.abspath(__file__))
BG, FG, DIM = "#2d333b", "#adbac7", "#768390"
BLUE, GREEN, ORANGE, PURPLE, RED, GRAY = "#388bfd", "#3fb950", "#f0883e", "#a371f7", "#f85149", "#636e7b"


def section(txt, tag):
    m = re.search(r"== " + tag + r".*?\n(.*?)(?=\n== |\Z)", txt, re.S)
    return [l.split() for l in m.group(1).strip().split("\n")]


def rows(sec):
    out = []
    for r in sec:
        try:
            out.append([float(x) for x in r if x != "|"])
        except ValueError:
            continue
    return out


def main():
    txt = open(os.path.join(HERE, "results.txt")).read()
    e2 = rows(section(txt, "E2"))
    std = [(r[3], r[0]) for r in e2]                # (fpr, bits/key)
    blk = [(r[8], r[7]) for r in e2]
    cf = [(r[3], r[2]) for r in rows(section(txt, "E4"))]
    xr = [(r[2], r[1]) for r in rows(section(txt, "E5"))[:9]]
    rb = [(r[2], r[1]) for r in rows(section(txt, "E7"))]

    plt.rcParams.update({"svg.hashsalt": "bloom-family", "font.family": "monospace", "font.size": 10, "text.color": FG,
                         "axes.labelcolor": FG, "xtick.color": FG, "ytick.color": FG})
    fig, axes = plt.subplots(1, 2, figsize=(12, 6.4), facecolor=BG)
    eps = [10 ** (-x / 20) for x in range(4, 125)]
    lb = [math.log2(1 / e) for e in eps]
    series = [("standard Bloom", std, BLUE, "o"), ("blocked Bloom (512-bit)", blk, ORANGE, "s"),
              ("cuckoo b=4 (no semi-sort)", cf, PURPLE, "^"), ("xor (1.23n+32)", xr, GREEN, "D"),
              ("standard Ribbon w=64, m=1.1n", rb, RED, "v")]
    for ax, rel in zip(axes, (False, True)):
        ax.set_facecolor(BG)
        for s in ax.spines.values():
            s.set_color(GRAY)
        ax.grid(color=GRAY, alpha=0.35, lw=0.6)
        ax.set_xscale("log")
        ax.invert_xaxis()
        f = (lambda e, b: b / math.log2(1 / e)) if rel else (lambda e, b: b)
        ax.plot(eps, [f(e, l) for e, l in zip(eps, lb)], color=FG, lw=1.4, label="lower bound log2(1/eps)")
        ax.plot(eps, [f(e, 1.44 * l) for e, l in zip(eps, lb)], color=BLUE, lw=1, ls="--",
                label="Bloom formula 1.44 log2(1/eps)")
        ax.plot(eps, [f(e, (l + 2) / 0.955) for e, l in zip(eps, lb)], color=PURPLE, lw=1, ls=":",
                label="cuckoo + semi-sort (paper), (log2(1/eps)+2)/0.955")
        ax.plot(eps, [f(e, 1.125 * l) for e, l in zip(eps, lb)], color=GREEN, lw=1, ls=":",
                label="binary fuse 3-wise (paper), 1.125 log2(1/eps)")
        for name, pts, col, mk in series:
            pts = [p for p in pts if p[0] > 0]
            ax.plot([p[0] for p in pts], [f(*p) for p in pts], color=col, marker=mk, ms=5, lw=1.2, label=name)
        ax.set_xlabel("false positive rate (log scale)")
        if rel:
            ax.set_ylabel("bits per key / log2(1/FPR)")
            ax.set_ylim(0.9, 2.0)
            ax.set_title("space relative to the lower bound", color=FG)
        else:
            ax.set_ylabel("bits per key")
            ax.set_ylim(0, 26)
            ax.set_title("measured space vs FPR, n = 2^20 keys", color=FG)
    h, l = axes[0].get_legend_handles_labels()
    leg = fig.legend(h, l, fontsize=8.5, facecolor=BG, edgecolor=GRAY, loc="lower center", ncol=3)
    for t in leg.get_texts():
        t.set_color(FG)
    fig.tight_layout(rect=(0, 0.13, 1, 1))
    fig.savefig(os.path.join(HERE, "..", "space-fpr.svg"), facecolor=BG, metadata={"Date": None})
    print("wrote space-fpr.svg")


if __name__ == "__main__":
    main()
