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


def open_text(path):
    if path.endswith(".xz"):
        return lzma.open(path, "rt", encoding="ascii")
    return open(path, "rt", encoding="ascii")


def norm_clause(lits):
    return tuple(lits)


def gate_kind(clauses):
    s = set(clauses)
    if len(s) != 3:
        return None
    vars_ = sorted({abs(x) for c in s for x in c})
    if len(vars_) != 3:
        return None
    z = max(vars_)
    x, y = [v for v in vars_ if v != z]
    # z = x OR y: (x y -z), (-x z), (-y z)
    if {(x, y, -z), (-x, z), (-y, z)} == s:
        return "or"
    # z = x AND y: (-x -y z), (x -z), (y -z)
    if {(-x, -y, z), (x, -z), (y, -z)} == s:
        return "and"
    # z = NAND(x,y): (-x -y -z), (x z), (y z)
    if {(-x, -y, -z), (x, z), (y, z)} == s:
        return "nand"
    # z = NOR(x,y): (x y z), (-x -z), (-y -z)
    if {(x, y, z), (-x, -z), (-y, -z)} == s:
        return "nor"
    return None


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("paths", nargs="+")
    ap.add_argument("--limit-clauses", type=int, default=0)
    ap.add_argument("--gate-window", type=int, default=3)
    args = ap.parse_args()

    for path in args.paths:
        nvars = nclauses = 0
        seen = 0
        hist = collections.Counter()
        pos = neg = 0
        units = collections.Counter()
        first_units = []
        gate_counts = collections.Counter()
        unmatched = 0
        backwards = 0
        max_seen_var = 0
        first_free = []
        degree_sample = collections.Counter()
        buf = []
        with open_text(path) as f:
            for line in f:
                if not line or line[0] == "c":
                    continue
                if line[0] == "p":
                    parts = line.split()
                    nvars, nclauses = int(parts[2]), int(parts[3])
                    continue
                vals = [int(x) for x in line.split()]
                if not vals:
                    continue
                if vals[-1] == 0:
                    vals.pop()
                if not vals:
                    continue
                seen += 1
                hist[len(vals)] += 1
                for lit in vals:
                    if lit > 0:
                        pos += 1
                    else:
                        neg += 1
                    v = abs(lit)
                    if v <= 200000:
                        degree_sample[v] += 1
                if len(vals) == 1:
                    units[vals[0]] += 1
                    if len(first_units) < 40:
                        first_units.append(vals[0])
                if any(abs(l) > max_seen_var + 1000000 for l in vals):
                    backwards += 1
                max_seen_var = max(max_seen_var, *(abs(l) for l in vals))
                buf.append(norm_clause(vals))
                if len(buf) == args.gate_window:
                    k = gate_kind(buf)
                    if k:
                        gate_counts[k] += 1
                    else:
                        unmatched += 1
                    buf.clear()
                if args.limit_clauses and seen >= args.limit_clauses:
                    break
        if buf:
            unmatched += 1

        base = os.path.basename(path)
        print(f"{base}: vars={nvars} clauses={nclauses} scanned={seen}")
        print(f"  sizes={dict(sorted(hist.items()))} pos={pos} neg={neg}")
        print(f"  units={sum(units.values())} distinct_units={len(units)} first_units={first_units[:20]}")
        print(f"  gates={dict(gate_counts)} unmatched_windows={unmatched} backwards_jumps={backwards}")
        if degree_sample:
            degs = list(degree_sample.values())
            print(
                "  sample_degree "
                f"vars={len(degs)} min={min(degs)} max={max(degs)} "
                f"avg={sum(degs)/len(degs):.2f}"
            )


if __name__ == "__main__":
    main()
