"""Stage 5: cross-check of the boundary formula F(n,p,b) (Lemma 3.4, project
encoder code/sat_or_ilp_solver.py:boundary_clauses) against the exhaustive
boundary distribution computed by code/robustness/beta_brute.c (no SAT, no
symmetry reduction).

For every (n,p) in the beta_brute output:
  * beta_brute's minimum must equal the SAT-derived table value in
    results/tables/boundary_table.csv wherever the table has an exact entry
    (entries '12' in the table mean '>= 12' and are compared accordingly);
  * F(n,p,b) must be UNSAT for b = beta - 1 and SAT for b = beta (both the
    'exactly p' and the 'at most b' sides of the encoding are exercised on
    UNSAT instances, which is the direction the proof relies on);
  * the model returned at b = beta must be a p-set of boundary <= beta by
    direct counting here.
Output: results/robustness/boundary_encoding_check.json
"""
import csv
import json
import os
import re
import sys

ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.chdir(ROOT)
sys.path.insert(0, os.path.join(ROOT, ".pydeps"))
sys.path.insert(0, os.path.join(ROOT, "code"))

from pysat.solvers import Solver  # noqa: E402
from sat_or_ilp_solver import boundary_clauses  # noqa: E402


def nbrs(n, k=5):
    A = {}
    for i in range(n):
        A[i] = {(i + 1) % n, (i - 1) % n, n + i}
        A[n + i] = {n + (i + k) % n, n + (i - k) % n, i}
    return A


def solve(n, p, b):
    cl, x, z, nv = boundary_clauses(n, 5, p, b)
    with Solver(name="cadical195", bootstrap_with=cl) as sv:
        sat = sv.solve()
        model = sv.get_model() if sat else None
    X = None
    if sat:
        X = [w for w in range(2 * n) if model[x[w] - 1] > 0]
    return sat, X


def main():
    table = {}
    rows = list(csv.DictReader(open("results/tables/boundary_table.csv")))
    for r in rows:
        p = int(r["p"])
        for col, val in r.items():
            if col != "p" and val not in ("", None):
                table[(int(col), p)] = 12 if val.strip().startswith(">=") else int(val)
    brute = {}
    for line in open("results/robustness/beta_brute_stdout.txt"):
        m = re.match(r"n=(\d+) k=5 p=(\d+) subsets=(\d+) beta=(-?\d+) hist=(.*)", line)
        if m:
            n, p, subs, beta = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
            hist = dict(int(a) for a in ()) if False else {int(h.split(":")[0]): int(h.split(":")[1]) for h in m.group(5).split()}
            brute[(n, p)] = (beta, subs, hist)
    out = {"instances": [], "table_comparisons": []}
    A_cache = {}
    for (n, p), (beta, subs, hist) in sorted(brute.items()):
        A = A_cache.setdefault(n, nbrs(n))
        rec = {"n": n, "p": p, "beta_brute": beta, "subsets": subs}
        if (n, p) in table:
            tv = table[(n, p)]
            rec["table_value"] = tv
            rec["table_consistent"] = (tv == beta) if tv <= 11 else (beta >= 12)
            out["table_comparisons"].append(rec["table_consistent"])
        # SAT at beta, UNSAT at beta-1
        s_lo, _ = solve(n, p, beta - 1) if beta >= 1 else (False, None)
        s_hi, X = solve(n, p, beta)
        rec["unsat_at_beta_minus_1"] = (s_lo is False)
        rec["sat_at_beta"] = bool(s_hi)
        if s_hi:
            Xs = set(X)
            bd = {w for x in X for w in A[x] if w not in Xs}
            rec["model_size_ok"] = len(X) == p
            rec["model_boundary"] = len(bd)
            rec["model_boundary_ok"] = len(bd) <= beta
        rec["ok"] = rec["unsat_at_beta_minus_1"] and rec["sat_at_beta"] and rec.get("model_size_ok", False) and rec.get("model_boundary_ok", False) and rec.get("table_consistent", True)
        out["instances"].append(rec)
    out["num_instances"] = len(out["instances"])
    out["all_ok"] = all(r["ok"] for r in out["instances"])
    out["table_cells_compared"] = len(out["table_comparisons"])
    out["table_cells_consistent"] = sum(out["table_comparisons"])
    json.dump(out, open("results/robustness/boundary_encoding_check.json", "w"), indent=1)
    print("instances:", out["num_instances"], "all ok:", out["all_ok"], "table cells compared:", out["table_cells_compared"], "consistent:", out["table_cells_consistent"])
    for r in out["instances"]:
        if not r["ok"]:
            print("FAIL", r)


if __name__ == "__main__":
    main()
