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


def open_maybe_xz(path):
    if path.endswith(".xz"):
        return lzma.open(path, "rt")
    return open(path, "rt")


def parse_dimacs(path):
    clauses = []
    nvars = nclauses = None
    with open_maybe_xz(path) as f:
        for line in f:
            if not line or line[0] == "c":
                continue
            parts = line.split()
            if not parts:
                continue
            if parts[0] == "p":
                nvars = int(parts[2])
                nclauses = int(parts[3])
            else:
                lits = [int(x) for x in parts if x != "0"]
                if lits:
                    clauses.append(lits)
    if nclauses is not None and nclauses != len(clauses):
        raise ValueError((nclauses, len(clauses)))
    return nvars, clauses


def high_var(lit):
    v = abs(lit)
    if v % 2 != 1:
        raise ValueError(f"expected odd first rail, got {lit}")
    return (v + 1) // 2


def recover_high_clauses(clauses):
    groups = collections.defaultdict(list)
    for cid, cl in enumerate(clauses, 1):
        if len(cl) not in (4, 6):
            raise ValueError(f"bad size {len(cl)} at {cid}")
        key = []
        orient = []
        for i in range(0, len(cl), 2):
            a, b = cl[i], cl[i + 1]
            if abs(a) + 1 != abs(b) or min(abs(a), abs(b)) % 2 != 1:
                raise ValueError(f"bad rail pair at {cid}: {a} {b}")
            if (a > 0) == (b > 0):
                lit = high_var(a)
                bit = 1 if a > 0 else 0
            else:
                lit = -high_var(a)
                bit = 1 if a > 0 else 0
            key.append(lit)
            orient.append(bit)
        groups[tuple(key)].append((tuple(orient), cid))
    high = []
    for key, vals in groups.items():
        k = len(key)
        seen = {o for o, _ in vals}
        if len(vals) != (1 << k) or len(seen) != (1 << k):
            raise ValueError(f"incomplete group {key}: {len(vals)} clauses")
        high.append((key, tuple(cid for _, cid in sorted(vals))))
    return high


def infer_order_n(high_vars):
    # high_vars = n * (n - 1)
    disc = 1 + 4 * high_vars
    root = math.isqrt(disc)
    if root * root != disc or (1 + root) % 2:
        return None
    return (1 + root) // 2


def hid(i, j, n):
    if i == j:
        raise ValueError("diagonal")
    return i * (n - 1) + (j if j < i else j - 1) + 1


def classify(high, high_vars):
    n = infer_order_n(high_vars)
    by_key = {tuple(k): cids for k, cids in high}
    missing = []
    preds = [None] * n
    if n is None:
        return None
    for i in range(n):
        for j in range(i + 1, n):
            if tuple(sorted((-hid(i, j, n), -hid(j, i, n)), key=abs)) not in by_key and (-hid(i, j, n), -hid(j, i, n)) not in by_key and (-hid(j, i, n), -hid(i, j, n)) not in by_key:
                missing.append(("antisym", i, j))
    trans_missing = 0
    for i in range(n):
        for j in range(n):
            if i == j:
                continue
            for k in range(n):
                if k == i or k == j:
                    continue
                key = (-hid(i, j, n), -hid(j, k, n), hid(i, k, n))
                if key not in by_key:
                    trans_missing += 1
    for key in by_key:
        if len(key) == 3 and all(x > 0 for x in key):
            js = []
            ps = []
            for h in key:
                hh = h - 1
                src = hh // (n - 1)
                r = hh % (n - 1)
                dst = r if r < src else r + 1
                js.append(dst)
                ps.append(src)
            if len(set(js)) == 1:
                preds[js[0]] = ps
    return n, missing, trans_missing, preds


def main():
    for path in sys.argv[1:]:
        nvars, clauses = parse_dimacs(path)
        high = recover_high_clauses(clauses)
        high_vars = nvars // 2
        info = classify(high, high_vars)
        print(path)
        print(" high_vars", high_vars, "high_clauses", len(high), "sizes", collections.Counter(len(k) for k, _ in high))
        print(" classify", info[:3] if info else None)
        if info:
            n, _, _, preds = info
            print(" preds_complete", all(p is not None for p in preds))


if __name__ == "__main__":
    main()
