#!/usr/bin/env python3
import lzma
import sys
from collections import Counter, defaultdict


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


def build_side(side_clauses):
    aux = sorted({abs(l) for c in side_clauses for l in c if abs(l) > 300})
    assert len(aux) == 4, aux
    amap = {v: i + 1 for i, v in enumerate(aux)}
    aclauses = []
    for c in side_clauses:
        prim = None
        alits = []
        for lit in c:
            v = abs(lit)
            if v <= 300:
                prim = lit
            else:
                alits.append(amap[v] if lit > 0 else -amap[v])
        assert prim is not None
        aclauses.append((prim, alits))
    sat_masks = []
    for bits in range(16):
        mask = 0
        for i, (_, alits) in enumerate(aclauses):
            for lit in alits:
                idx = abs(lit) - 1
                aval = (bits >> idx) & 1
                if (lit > 0) == aval:
                    mask |= 1 << i
                    break
        sat_masks.append(mask)
    subset_sat = [False] * (1 << 12)
    for mask in range(1 << 12):
        subset_sat[mask] = any((sm & mask) == mask for sm in sat_masks)
    cores = []
    for mask in range(1, 1 << 12):
        if subset_sat[mask]:
            continue
        if any(((mask >> i) & 1) and not subset_sat[mask ^ (1 << i)] for i in range(12)):
            continue
        cores.append(tuple(aclauses[i][0] for i in range(12) if (mask >> i) & 1))
    terms = []
    for bits in range(16):
        req = []
        ok = True
        for prim, alits in aclauses:
            auxsat = False
            for lit in alits:
                idx = abs(lit) - 1
                aval = (bits >> idx) & 1
                if (lit > 0) == aval:
                    auxsat = True
                    break
            if not auxsat:
                if -prim in req:
                    ok = False
                    break
                if prim not in req:
                    req.append(prim)
        if ok:
            terms.append(tuple(req))
    return aux, cores, terms


def add_clause(a, b):
    out = []
    for lit in a + b:
        if -lit in out:
            return None
        if lit not in out:
            out.append(lit)
    return tuple(out)


def project(n, clauses):
    assert (n - 300) % 9 == 0
    nb = (n - 300) // 9
    occ = Counter(abs(l) for c in clauses for l in c)
    pclauses = []
    side_core_hist = Counter()
    side_term_hist = Counter()
    block_pvars = Counter()
    for b in range(nb):
        t = 301 + 9 * b + 8
        assert occ[t] == 24
        sides = {1: [], -1: []}
        pvars = set()
        for c in clauses:
            sign = 0
            for lit in c:
                if lit == t:
                    sign = 1
                elif lit == -t:
                    sign = -1
            if not sign:
                continue
            dropped = [lit for lit in c if abs(lit) != t]
            for lit in dropped:
                if abs(lit) <= 300:
                    pvars.add(abs(lit))
            sides[sign].append(dropped)
        _, cores_pos, terms_pos = build_side(sides[1])
        _, cores_neg, terms_neg = build_side(sides[-1])
        side_core_hist[len(cores_pos)] += 1
        side_core_hist[len(cores_neg)] += 1
        side_term_hist[len(terms_pos)] += 1
        side_term_hist[len(terms_neg)] += 1
        block_pvars[len(pvars)] += 1
        for c0 in cores_pos:
            for c1 in cores_neg:
                pc = add_clause(c0, c1)
                if pc is not None:
                    pclauses.append(pc)
    return pclauses, side_core_hist, side_term_hist, block_pvars


def main():
    for path in sys.argv[1:]:
        n, clauses = parse(path)
        pclauses, core_hist, term_hist, block_pvars = project(n, clauses)
        lens = Counter(len(c) for c in pclauses)
        litocc = Counter(abs(l) for c in pclauses for l in c)
        pol = Counter(1 if l > 0 else -1 for c in pclauses for l in c)
        print(path)
        print("  projected", len(pclauses), "lens", dict(sorted(lens.items())))
        print("  side_cores", dict(sorted(core_hist.items())), "side_terms", dict(sorted(term_hist.items())))
        print("  block_pvars", dict(sorted(block_pvars.items())))
        print("  var_occ min/avg/max", min(litocc.values()), sum(litocc.values()) / 300.0, max(litocc.values()), "pol", dict(pol))


if __name__ == "__main__":
    main()
