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


def read_cnf(path):
    nvars = nclauses = None
    clauses = []
    with lzma.open(path, "rt") if path.endswith(".xz") else open(path, "rt") as f:
        cur = []
        for line in f:
            if not line or line[0] == "c":
                continue
            if line[0] == "p":
                parts = line.split()
                nvars = int(parts[2])
                nclauses = int(parts[3])
                continue
            for tok in line.split():
                lit = int(tok)
                if lit == 0:
                    clauses.append(cur)
                    cur = []
                else:
                    cur.append(lit)
    return nvars, nclauses, clauses


def lit_off(lit, off):
    return lit + off if lit > 0 else lit - off


def norm_block_clause(cl, off):
    return tuple(lit - off if lit > 0 else lit + off for lit in cl)


def main(paths):
    for path in paths:
        nvars, nclauses, clauses = read_cnf(path)
        b = nvars // 3
        sizes = collections.Counter(map(len, clauses))
        units = [cl[0] for cl in clauses if len(cl) == 1]
        print(path.split("/")[-1], "vars", nvars, "block", b, "clauses", nclauses, "sizes", dict(sizes), "units", units)

        by_size = {k: [] for k in [1, 2, 3]}
        for i, cl in enumerate(clauses):
            if len(cl) in by_size:
                by_size[len(cl)].append((i, tuple(cl)))

        # Count binary clauses whose vars lie in pairs of blocks.
        pair_counter = collections.Counter()
        eq_pos = collections.Counter()
        eq_neg = collections.Counter()
        for _, cl in by_size[2]:
            a, c = cl
            va, vc = abs(a), abs(c)
            ba, bc = (va - 1) // b, (vc - 1) // b
            pair_counter[tuple(sorted((ba, bc)))] += 1
            # Detect a=b pairs represented by (a or ~b), (~a or b).
            if ba != bc:
                oa, oc = (va - 1) % b + 1, (vc - 1) % b + 1
                if oa == oc:
                    if a * c < 0:
                        eq_pos[(min(ba, bc), max(ba, bc))] += 1
                    else:
                        eq_neg[(min(ba, bc), max(ba, bc))] += 1
        print("  binary block-pairs", dict(pair_counter))
        print("  same-index cross binaries opposite/sign", dict(eq_pos), dict(eq_neg))

        block_clauses = []
        for off in [0, b, 2 * b]:
            s = collections.Counter()
            for cl in clauses:
                if all(off < abs(l) <= off + b for l in cl):
                    s[norm_block_clause(cl, off)] += 1
            block_clauses.append(s)
        print("  in-block clause counts", [sum(c.values()) for c in block_clauses])
        print("  copy diff 0-1", len((block_clauses[0] - block_clauses[1]) + (block_clauses[1] - block_clauses[0])))
        print("  copy diff 0-2", len((block_clauses[0] - block_clauses[2]) + (block_clauses[2] - block_clauses[0])))

        # Gate-output range: every ternary with max var as output and matching two binaries.
        clset = set(tuple(cl) for cl in clauses)
        gates = 0
        outvars = []
        for cl in clauses:
            if len(cl) != 3:
                continue
            vals = sorted(set(abs(l) for l in cl))
            out = max(vals)
            out_lit = out if out in map(abs, cl) else None
            # Canonical two-input AND of signed literals: (a or -o), (b or -o), (-a or -b or o).
            if any(abs(l) == out and l > 0 for l in cl):
                inputs = [-l for l in cl if abs(l) != out]
                if len(inputs) == 2:
                    a, d = inputs
                    if tuple([a, -out]) in clset and tuple([d, -out]) in clset:
                        gates += 1
                        outvars.append(out)
        print("  recognized gates", gates, "out range", (min(outvars), max(outvars)) if outvars else None)


if __name__ == "__main__":
    main(sys.argv[1:])
