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


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


def parse(path):
    nvars = nclauses = None
    with open_formula(path) as f:
        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
            lits = [int(x) for x in line.split()]
            if not lits:
                continue
            if lits[-1] == 0:
                lits.pop()
            yield nvars, nclauses, lits


def main():
    if len(sys.argv) != 2:
        print("usage: profile_cnf.py formula.cnf[.xz]", file=sys.stderr)
        return 2
    path = sys.argv[1]
    size_hist = collections.Counter()
    sign_hist = collections.Counter()
    unit = []
    occ = collections.Counter()
    pos_occ = collections.Counter()
    neg_occ = collections.Counter()
    first = []
    total_lits = 0
    nvars = nclauses = None
    clause_count = 0
    max_var = 0
    binary_pairs = collections.Counter()
    ternary_patterns = collections.Counter()
    long_sizes = collections.Counter()

    for nv, nc, lits in parse(path):
        if nvars is None:
            nvars, nclauses = nv, nc
        clause_count += 1
        if len(first) < 80:
            first.append(lits)
        k = len(lits)
        size_hist[k] += 1
        if k >= 8:
            long_sizes[k] += 1
        total_lits += k
        p = sum(1 for x in lits if x > 0)
        sign_hist[(k, p)] += 1
        if k == 1:
            unit.append(lits[0])
        if k == 2:
            a, b = lits
            binary_pairs[(a > 0, b > 0)] += 1
        if k == 3:
            ternary_patterns[(tuple(x > 0 for x in lits))] += 1
        for x in lits:
            v = abs(x)
            max_var = max(max_var, v)
            occ[v] += 1
            if x > 0:
                pos_occ[v] += 1
            else:
                neg_occ[v] += 1

    print(f"path {path}")
    print(f"header vars={nvars} clauses={nclauses}; seen clauses={clause_count}; max_var={max_var}")
    print(f"total literals={total_lits}; avg size={total_lits / max(1, clause_count):.3f}")
    print("clause size histogram:")
    for k, v in size_hist.most_common():
        print(f"  {k}: {v}")
    print("sign histogram by (size,pos_count), top 30:")
    for key, v in sign_hist.most_common(30):
        print(f"  {key}: {v}")
    print("binary sign pair histogram:")
    for key, v in binary_pairs.most_common():
        print(f"  {key}: {v}")
    print("ternary sign tuple histogram:")
    for key, v in ternary_patterns.most_common(20):
        print(f"  {key}: {v}")
    print(f"unit count={len(unit)} first_units={unit[:80]}")
    print("long size histogram top:")
    for k, v in sorted(long_sizes.items())[:80]:
        print(f"  {k}: {v}")
    degs = list(occ.values())
    if degs:
        degs.sort()
        for q in [0, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99, 1.0]:
            idx = min(len(degs) - 1, int(q * (len(degs) - 1)))
            print(f"occ_q{q:g}={degs[idx]}")
        print("top occurrence variables:")
        for v, d in occ.most_common(30):
            print(f"  {v}: occ={d} pos={pos_occ[v]} neg={neg_occ[v]}")
    print("first clauses:")
    for i, c in enumerate(first, 1):
        print(f"  {i}: {' '.join(map(str, c))} 0")


if __name__ == "__main__":
    raise SystemExit(main())
