#!/usr/bin/env python3
import argparse
import lzma
import os
import sys


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


def lit_value(lit, val):
    v = val[abs(lit)]
    if v == 255:
        raise RuntimeError(f"unassigned {abs(lit)}")
    b = bool(v)
    return b if lit > 0 else not b


def allowed_for_z(z, clauses, val, preferred=False):
    ok_vals = []
    for zv in (0, 1):
        all_ok = True
        for c in clauses:
            sat = False
            for lit in c:
                a = abs(lit)
                if a == z:
                    b = bool(zv)
                    if lit < 0:
                        b = not b
                else:
                    vv = val[a]
                    if vv == 255:
                        # missing smaller variable means primary default false
                        vv = 0
                    b = bool(vv)
                    if lit < 0:
                        b = not b
                if b:
                    sat = True
                    break
            if not sat:
                all_ok = False
                break
        if all_ok:
            ok_vals.append(zv)
    if preferred in ok_vals:
        return int(preferred), ok_vals
    if ok_vals:
        return ok_vals[0], ok_vals
    return -1, []


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("path")
    ap.add_argument("--prefer", choices=["false", "true"], default="false")
    ap.add_argument("--check", action="store_true")
    args = ap.parse_args()
    nvars = 0
    current_z = 0
    group = []
    val = None
    units = []
    groups = conflicts = primary = forced = 0

    def ensure_to(v):
        nonlocal primary
        for i in range(1, v + 1):
            if val[i] < 0:
                val[i] = 0
                primary += 1

    def finish(z, clauses):
        nonlocal groups, conflicts, forced, primary
        if z <= 0 or not clauses:
            return True
        # Assign intervening primaries.
        for i in range(1, z):
            if val[i] < 0:
                val[i] = 0
                primary += 1
        unit_pref = None
        nonunits = []
        for c in clauses:
            if len(c) == 1:
                if abs(c[0]) == z:
                    unit_pref = c[0] > 0
                    units.append(c[0])
                else:
                    units.append(c[0])
            else:
                nonunits.append(c)
        if nonunits:
            chosen, ok = allowed_for_z(z, nonunits, val, args.prefer == "true")
            if chosen < 0:
                conflicts += 1
                print(f"conflict z={z} clauses={nonunits[:5]}", file=sys.stderr)
                return False
            if unit_pref is not None and chosen != int(unit_pref):
                conflicts += 1
                print(f"unit conflict z={z} chosen={chosen} pref={unit_pref}", file=sys.stderr)
                return False
            val[z] = chosen
            forced += len(ok) == 1
            groups += 1
        elif unit_pref is not None:
            val[z] = int(unit_pref)
        return True

    with open_text(args.path) as f:
        for line in f:
            if not line or line[0] == "c":
                continue
            if line[0] == "p":
                p = line.split()
                nvars = int(p[2])
                val = bytearray([255]) * (nvars + 1)
                continue
            lits = [int(x) for x in line.split()]
            if lits and lits[-1] == 0:
                lits.pop()
            if not lits:
                continue
            z = max(abs(x) for x in lits)
            if z < current_z and len(lits) == 1:
                # Repeated global constant unit; apply immediately.
                units.append(lits[0])
                v = abs(lits[0])
                if val[v] != 255 and val[v] != int(lits[0] > 0):
                    conflicts += 1
                    print(f"late unit conflict {lits[0]}", file=sys.stderr)
                    return 1
                val[v] = int(lits[0] > 0)
                continue
            if z != current_z:
                if not finish(current_z, group):
                    return 1
                current_z = z
                group = []
            group.append(tuple(lits))
        if not finish(current_z, group):
            return 1
    for i in range(1, nvars + 1):
        if val[i] == 255:
            val[i] = 0
            primary += 1
    bad_units = [u for u in units if lit_value(u, val) is False]
    print(
        f"{os.path.basename(args.path)} vars={nvars} groups={groups} "
        f"primary={primary} forced={forced} units={len(units)} bad_units={len(bad_units)} conflicts={conflicts}"
    )
    if bad_units[:10]:
        print("bad_units", bad_units[:10])
    if args.check:
        bad = 0
        with open_text(args.path) as f:
            for line in f:
                if not line or line[0] in "cp":
                    continue
                lits = [int(x) for x in line.split()]
                if lits and lits[-1] == 0:
                    lits.pop()
                if lits and not any(lit_value(x, val) for x in lits):
                    bad += 1
                    if bad <= 10:
                        print("bad_clause", line.strip())
        print("bad_clauses", bad)
    return 0


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