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


def parse(path):
    clauses = []
    with lzma.open(path, "rt") if str(path).endswith(".xz") else open(path) as f:
        for line in f:
            if line.startswith(("c", "p")) or not line.strip():
                continue
            clauses.append(tuple(int(t) for t in line.split() if t != "0"))
    return clauses


def infer_agents(clauses):
    first = clauses[0]
    return next(x for x in first if x > 0) - 1


def root_threshold(block, n_agents, unit_lit):
    neg_nonunit = set()
    pos_aux = set()
    for c in block:
        if len(c) == 1:
            continue
        for lit in c:
            v = abs(lit)
            if v <= n_agents:
                continue
            if lit > 0:
                pos_aux.add(v)
            else:
                neg_nonunit.add(v)
    roots = sorted(v for v in pos_aux if v not in neg_nonunit)
    if not roots:
        return None
    # The root outputs are the contiguous pure-positive run containing the unit.
    u = abs(unit_lit)
    if u not in roots:
        return None
    s = u
    while s - 1 in roots:
        s -= 1
    e = u
    while e + 1 in roots:
        e += 1
    length = e - s + 1
    pos = u - s + 1
    return s, e, length, pos


def extract(path):
    clauses = parse(path)
    n = infer_agents(clauses)
    units = [(i, c[0]) for i, c in enumerate(clauses) if len(c) == 1]
    first_pos_idx = next(i for i, (_, lit) in enumerate(units) if lit > 0)
    skills = []
    prev = 0
    for idx in range(first_pos_idx):
        ci, lit = units[idx]
        block = clauses[prev : ci + 1]
        agents = sorted({abs(x) for c in block for x in c if abs(x) <= n})
        rt = root_threshold(block, n, lit)
        if rt is None:
            req = None
        else:
            _, _, length, pos = rt
            req = len(agents) - (pos - 1)
        skills.append((agents, req, rt, lit))
        prev = ci + 1
    conflicts = []
    for c in clauses:
        if len(c) == 2 and c[0] < 0 and c[1] < 0 and abs(c[0]) <= n and abs(c[1]) <= n:
            conflicts.append((abs(c[0]), abs(c[1])))
    return n, skills, conflicts, units[first_pos_idx:], clauses


def main():
    for arg in sys.argv[1:]:
        n, skills, conflicts, units_after, clauses = extract(Path(arg))
        reqs = collections.Counter(req for _, req, _, _ in skills)
        sizes = collections.Counter(len(a) for a, _, _, _ in skills)
        deg = [0] * (n + 1)
        for agents, req, _, _ in skills:
            for a in agents:
                deg[a] += 1
        print("==", Path(arg).name)
        print("agents", n, "skills", len(skills), "reqs", dict(reqs), "skill_sizes", sizes.most_common(10))
        print("conflicts", len(conflicts), "after_units", units_after)
        print("agent skill degree", collections.Counter(deg[1:]).most_common(10), "max", max(deg[1:]))
        print("first skills", [(len(a), req, a[:12], rt) for a, req, rt, _ in skills[:5]])
        # Conflict components.
        parent = list(range(n + 1))
        def find(x):
            while parent[x] != x:
                parent[x] = parent[parent[x]]
                x = parent[x]
            return x
        def union(a, b):
            ra, rb = find(a), find(b)
            if ra != rb:
                parent[rb] = ra
        for a, b in conflicts:
            union(a, b)
        comps = collections.defaultdict(list)
        for i in range(1, n + 1):
            comps[find(i)].append(i)
        print("conflict comps", collections.Counter(len(v) for v in comps.values()).most_common(10), "num", len(comps))
        print()


if __name__ == "__main__":
    main()
