#!/usr/bin/env python3
import ast
import collections
import lzma
import re
import sys
from pathlib import Path


def read_formula(path):
    opener = lzma.open if str(path).endswith(".xz") else open
    comments = []
    clauses = []
    nvars = nclauses = None
    with opener(path, "rt") as f:
        for line in f:
            if not line or line[0] in "%0":
                continue
            if line[0] == "c":
                comments.append(line.rstrip("\n"))
                continue
            if line[0] == "p":
                parts = line.split()
                nvars, nclauses = int(parts[2]), int(parts[3])
                continue
            lits = [int(x) for x in line.split()]
            if lits and lits[-1] == 0:
                lits.pop()
            if lits:
                clauses.append(tuple(lits))
    return nvars, nclauses, clauses, comments


class ParityDSU:
    def __init__(self, n):
        self.parent = list(range(n + 1))
        self.rank = [0] * (n + 1)
        self.par = [0] * (n + 1)

    def find(self, x):
        if self.parent[x] == x:
            return x, 0
        r, p = self.find(self.parent[x])
        self.par[x] ^= p
        self.parent[x] = r
        return self.parent[x], self.par[x]

    def union(self, a, b, p):
        ra, pa = self.find(a)
        rb, pb = self.find(b)
        if ra == rb:
            return (pa ^ pb) == p
        if self.rank[ra] < self.rank[rb]:
            ra, rb = rb, ra
            a, b = b, a
            pa, pb = pb, pa
        self.parent[rb] = ra
        self.par[rb] = pa ^ pb ^ p
        if self.rank[ra] == self.rank[rb]:
            self.rank[ra] += 1
        return True


def lit_key(l):
    return abs(l), 1 if l < 0 else 0


def main():
    for arg in sys.argv[1:]:
        path = Path(arg)
        nvars, nclauses, clauses, comments = read_formula(path)
        print(f"== {path.name}")
        print(f"vars={nvars} clauses={nclauses} parsed={len(clauses)}")
        dist = collections.Counter(len(c) for c in clauses)
        print("lengths", " ".join(f"{k}:{dist[k]}" for k in sorted(dist)))

        # Detect initial selection bits as the unique long all-negative clause.
        long_neg = [c for c in clauses if len(c) > 10 and all(l < 0 for l in c)]
        if long_neg:
            print("long negative clauses:", [len(c) for c in long_neg[:5]])
            selectors = sorted(abs(l) for l in long_neg[0])
        else:
            selectors = []
        print("selectors", selectors[:10], "count", len(selectors))

        pairs = collections.defaultdict(set)
        for c in clauses:
            if len(c) == 2:
                a, b = c
                pairs[tuple(sorted((abs(a), abs(b))))].add((a > 0, b > 0, abs(a) <= abs(b)))

        dsu = ParityDSU(nvars)
        eq_pairs = 0
        neq_pairs = 0
        by_pair = collections.defaultdict(set)
        for c in clauses:
            if len(c) == 2:
                a, b = c
                aa, sa = lit_key(a)
                bb, sb = lit_key(b)
                if aa == bb:
                    continue
                key = tuple(sorted((aa, bb)))
                by_pair[key].add((a, b))

        for (a, b), cs in by_pair.items():
            s = set(cs)
            # x == y: (-x or y) and (x or -y)
            if ((-a, b) in s or (b, -a) in s) and ((a, -b) in s or (-b, a) in s):
                dsu.union(a, b, 0)
                eq_pairs += 1
            # x == not y: (-x or -y) and (x or y)
            if ((-a, -b) in s or (-b, -a) in s) and ((a, b) in s or (b, a) in s):
                dsu.union(a, b, 1)
                neq_pairs += 1

        classes = collections.defaultdict(list)
        for v in range(1, nvars + 1):
            r, p = dsu.find(v)
            classes[r].append((v, p))
        sizes = sorted((len(vs), r) for r, vs in classes.items() if len(vs) > 1)
        print(f"binary eq pairs={eq_pairs} neq_pairs={neq_pairs} nontriv_classes={len(sizes)}")
        print("largest classes", sizes[-10:])
        if selectors:
            for x in selectors[: min(8, len(selectors))]:
                r, px = dsu.find(x)
                members = classes[r]
                pos = sum(1 for _, p in members if p == px)
                neg = len(members) - pos
                print(f" selector {x}: class_size={len(members)} same={pos} opposite={neg}")

        text = "\n".join(comments)
        m = re.search(r"universe = (\[.*?\])", text)
        mt = re.search(r"t = (\d+)", text)
        if m:
            nums = ast.literal_eval(m.group(1))
            t = int(mt.group(1))
            print(
                "comment hint:",
                f"items={len(nums)}",
                f"maxbit={max(nums + [t]).bit_length()}",
                "popcounts=" + ",".join(str(x.bit_count()) for x in nums[: min(8, len(nums))]),
                f"tpop={t.bit_count()}",
            )


if __name__ == "__main__":
    main()
