#!/usr/bin/env python3
import lzma
import math
import os
import sys


def iter_clauses(path):
    opener = lzma.open if path.endswith(".xz") else open
    with opener(path, "rt", encoding="ascii", errors="replace") as f:
        for line in f:
            if line.startswith("c") or line.startswith("p"):
                continue
            vals = [int(x) for x in line.split()]
            if vals and vals[-1] == 0:
                vals.pop()
            if vals:
                yield vals


def detect_n(path):
    it = iter(iter_clauses(path))
    count = 0
    while True:
        try:
            c1, c2, c3 = next(it), next(it), next(it)
        except StopIteration:
            break
        if (
            len(c1) == len(c2) == len(c3) == 2
            and c1[0] < 0 and c1[1] < 0
            and c2[0] < 0 and c2[1] < 0
            and c3[0] > 0 and c3[1] < 0
            and -c2[0] == c3[0]
            and c1[0] == c2[1]
            and c1[1] == c3[1]
        ):
            count += 1
        else:
            break
    n = int((1 + math.isqrt(1 + 8 * count)) // 2)
    assert n * (n - 1) // 2 == count, (path, count, n)
    return n


def amap(n, order_count, i, j):
    return order_count + 1 + i * n + j


def pmap(n, p_start, i, j):
    return p_start + i * n + j


def wmap(m, w_start, i, e):
    return w_start + i * m + e


def detect_m_and_ranges(path, n):
    order_count = n * (n - 1) // 2
    a_start = order_count + 1
    a_end = a_start + n * n - 1
    w_start = a_end + 1
    # Find the first pure-improved subset clause of the form
    # -a(i,j) -p(i,j) -w(i,e) w(j,e).
    for cl in iter_clauses(path):
        if (
            len(cl) == 4
            and cl[0] < 0
            and a_start <= -cl[0] <= a_end
            and cl[1] < 0
            and cl[2] < 0
            and -cl[2] == w_start
            and cl[3] > w_start
        ):
            m = cl[3] - w_start
            p_start = -cl[1]
            return m, w_start, w_start + n * m - 1, p_start, p_start + n * (n - 1) - 1
    raise RuntimeError("could not detect m/ranges")


def extract(path):
    n = detect_n(path)
    order_count = n * (n - 1) // 2
    m, w_start, w_end, p_start, p_end = detect_m_and_ranges(path, n)
    edges = [set() for _ in range(m)]
    primal = set()
    def p_to_pair(var):
        off = var - p_start
        i = off // (n - 1)
        c = off % (n - 1)
        j = c if c < i else c + 1
        return i, j

    # Incidence clauses from improved pure encoding:
    # p(i,j) OR not w(j,e), for every vertex i in edge e.
    for cl in iter_clauses(path):
        if len(cl) == 2:
            a, b = cl
            if a > 0 and p_start <= a <= p_end and b < 0 and w_start <= -b <= w_end:
                vi, vj = p_to_pair(a)
                y = -b - w_start
                row, e = divmod(y, m)
                if row == vj:
                    edges[e].add(vi)
            elif b > 0 and p_start <= b <= p_end and a < 0 and w_start <= -a <= w_end:
                vi, vj = p_to_pair(b)
                y = -a - w_start
                row, e = divmod(y, m)
                if row == vj:
                    edges[e].add(vi)
    for e, verts in enumerate(edges):
        vs = sorted(verts)
        for i, u in enumerate(vs):
            for v in vs[i+1:]:
                primal.add((u, v))
    return n, m, w_start, p_start, edges, primal


def main():
    for path in sys.argv[1:]:
        n, m, w_start, p_start, edges, primal = extract(path)
        sizes = sorted(len(e) for e in edges)
        deg = [0] * n
        for e in edges:
            for v in e:
                deg[v] += 1
        print(os.path.basename(path), f"n={n}", f"m={m}", f"w_start={w_start}", f"p_start={p_start}", f"primal_edges={len(primal)}")
        print(" edge_size_hist", {s: sizes.count(s) for s in sorted(set(sizes))})
        print(" vertex_deg_min_med_max", min(deg), sorted(deg)[len(deg)//2], max(deg))
        print(" first_edges", [sorted(e) for e in edges[:10]])


if __name__ == "__main__":
    main()
