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


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


def profile(path, sample_limit=0):
    nvars = nclauses = None
    size_hist = collections.Counter()
    sign_hist = collections.Counter()
    clause_type = collections.Counter()
    occ = collections.Counter()
    pos_occ = collections.Counter()
    neg_occ = collections.Counter()
    first_vars = []
    last_vars = []
    unit_lits = []
    binary = []
    ternary_sample = collections.Counter()
    distinct_vars_per_clause = collections.Counter()
    parsed = 0
    with open_dimacs(path) as fh:
        cur = []
        for line in fh:
            if not line or line[0] == "c":
                continue
            if line[0] == "p":
                p = line.split()
                nvars, nclauses = int(p[2]), int(p[3])
                continue
            for tok in line.split():
                lit = int(tok)
                if lit == 0:
                    parsed += 1
                    k = len(cur)
                    size_hist[k] += 1
                    dvars = len({abs(x) for x in cur})
                    distinct_vars_per_clause[dvars] += 1
                    pos = sum(1 for x in cur if x > 0)
                    neg = k - pos
                    sign_hist[(neg, pos)] += 1
                    if k == 1:
                        unit_lits.append(cur[0])
                    elif k == 2:
                        binary.append(tuple(cur))
                    elif k == 3:
                        sig = tuple(1 if x > 0 else -1 for x in cur)
                        clause_type[sig] += 1
                        if len(ternary_sample) < 50:
                            ternary_sample[tuple(cur)] += 1
                    for x in cur:
                        v = abs(x)
                        occ[v] += 1
                        if x > 0:
                            pos_occ[v] += 1
                        else:
                            neg_occ[v] += 1
                    if parsed <= 30:
                        first_vars.append(cur[:])
                    last_vars.append(cur[:])
                    if len(last_vars) > 30:
                        last_vars.pop(0)
                    cur = []
                    if sample_limit and parsed >= sample_limit:
                        break
                else:
                    cur.append(lit)
            if sample_limit and parsed >= sample_limit:
                break
    top_occ = occ.most_common(20)
    unused = 0
    if nvars is not None:
        unused = sum(1 for v in range(1, nvars + 1) if occ[v] == 0)
    degrees = list(occ.values())
    deg_stats = {}
    if degrees:
        degrees.sort()
        deg_stats = {
            "min": degrees[0],
            "p50": degrees[len(degrees)//2],
            "p90": degrees[int(len(degrees)*0.9)],
            "p99": degrees[int(len(degrees)*0.99)],
            "max": degrees[-1],
            "mean": sum(degrees)/len(degrees),
        }
    return {
        "file": os.path.basename(path),
        "nvars": nvars,
        "nclauses": nclauses,
        "parsed": parsed,
        "size_hist": dict(sorted(size_hist.items())),
        "sign_hist": {f"{k[0]}neg_{k[1]}pos": v for k, v in sorted(sign_hist.items())},
        "ternary_sign_order": {"".join("+" if s > 0 else "-" for s in k): v for k, v in clause_type.most_common()},
        "distinct_vars_per_clause": dict(sorted(distinct_vars_per_clause.items())),
        "units": unit_lits[:50],
        "num_units": len(unit_lits),
        "num_binary": len(binary),
        "top_occ": top_occ,
        "unused": unused,
        "deg_stats": deg_stats,
        "first_clauses": first_vars[:10],
        "last_clauses": last_vars[-10:],
    }


def main():
    sample_limit = int(os.environ.get("SAMPLE_LIMIT", "0"))
    for path in sys.argv[1:]:
        print(json.dumps(profile(path, sample_limit), sort_keys=False))


if __name__ == "__main__":
    main()
