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


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


def infer_n_agents(clauses):
    for c in clauses[:1000]:
        pos = [x for x in c if x > 0]
        if pos:
            return min(pos) - 1
    raise ValueError("no positive aux")


def unit_positions(clauses):
    return [(i, c[0]) for i, c in enumerate(clauses) if len(c) == 1]


def propagate(nvars, clauses, fixed):
    val = [0] * (nvars + 1)
    q = collections.deque()

    def assign_lit(lit):
        v = abs(lit)
        b = 1 if lit > 0 else -1
        if val[v] == b:
            return True
        if val[v] == -b:
            return False
        val[v] = b
        q.append(v)
        return True

    for lit in fixed:
        if not assign_lit(lit):
            return False
    changed = True
    while changed:
        changed = False
        for c in clauses:
            sat = False
            un = 0
            last = 0
            for lit in c:
                v = abs(lit)
                if val[v] == 0:
                    un += 1
                    last = lit
                elif (lit > 0 and val[v] > 0) or (lit < 0 and val[v] < 0):
                    sat = True
                    break
            if sat:
                continue
            if un == 0:
                return False
            if un == 1:
                if not assign_lit(last):
                    return False
                changed = True
    return True


def main():
    for arg in sys.argv[1:]:
        path = Path(arg)
        comments, nvars, clauses = parse(path)
        n = infer_n_agents(clauses)
        units = unit_positions(clauses)
        first_pos_unit = next((i for i, lit in units if lit > 0), None)
        if first_pos_unit is None:
            print(path.name, "no positive unit")
            continue
        tail = clauses[first_pos_unit + 1 :]
        # The objective bound is present in comments for training only; use it to validate.
        bound = None
        for c in comments:
            m = re.search(r"upper bound.*?:\s*(\d+)", c)
            if m:
                bound = int(m.group(1))
        print("\n==", path.name, "n", n, "tail_clauses", len(tail), "comment_bound", bound)
        for t in ([0, 1, 2, 5, 10, 20, 30, 50, 80, 110] if bound is None else [0, max(0, bound-1), bound, bound+1, bound+5]):
            if t > n:
                continue
            ok = []
            for rep in range(5):
                rng = random.Random(1000 + t * 17 + rep)
                sel = set(rng.sample(range(1, n + 1), t))
                fixed = [i if i in sel else -i for i in range(1, n + 1)]
                ok.append(propagate(nvars, tail, fixed))
            print("size", t, ok)


if __name__ == "__main__":
    main()
