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


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


def status(lit, asg):
    v = abs(lit)
    if asg[v] is None:
        return None
    return (asg[v] if lit > 0 else not asg[v])


def main(path):
    nvars, clauses = parse(path)
    asg = [None] * (nvars + 1)
    long = 0
    for cl in clauses:
        if len(cl) >= 10:
            long += 1
            for i, lit in enumerate(cl):
                want = lit > 0
                if i + 1 != len(cl):
                    want = not want
                v = abs(lit)
                if asg[v] is not None and asg[v] != want:
                    print("conflict hint", v)
                    return
                asg[v] = want
    occ = [[] for _ in range(nvars + 1)]
    for ci, cl in enumerate(clauses):
        for lit in cl:
            occ[abs(lit)].append(ci)
    q = deque(range(len(clauses)))
    inq = bytearray([1]) * len(clauses)
    contradiction = False
    while q:
        ci = q.popleft()
        inq[ci] = 0
        sat = False
        un = []
        for lit in clauses[ci]:
            st = status(lit, asg)
            if st is True:
                sat = True
                break
            if st is None:
                un.append(lit)
        if sat:
            continue
        if not un:
            contradiction = True
            print("contradiction", ci, len(clauses[ci]), clauses[ci])
            break
        if len(un) == 1:
            lit = un[0]
            v = abs(lit)
            want = lit > 0
            asg[v] = want
            for cj in occ[v]:
                if not inq[cj]:
                    inq[cj] = 1
                    q.append(cj)
    rem = Counter()
    uns_false = []
    unresolved = []
    default_false_bad = Counter()
    for ci, cl in enumerate(clauses):
        sat = False
        un = 0
        for lit in cl:
            st = status(lit, asg)
            if st is True:
                sat = True
                break
            if st is None:
                un += 1
        if not sat:
            rem[un] += 1
            if len(unresolved) < 8:
                unresolved.append((ci, un, cl))
            ok_default = False
            for lit in cl:
                v = abs(lit)
                val = asg[v] if asg[v] is not None else False
                if (val if lit > 0 else not val):
                    ok_default = True
                    break
            if not ok_default:
                default_false_bad[len(cl)] += 1
                if len(uns_false) < 8:
                    uns_false.append((ci, un, cl))
    print("nvars", nvars, "clauses", len(clauses), "long", long, "assigned", sum(x is not None for x in asg), "contra", contradiction)
    print("remaining_unassigned_count", sorted(rem.items()))
    print("default_false_bad", sorted(default_false_bad.items()))
    print("first unresolved")
    for item in unresolved:
        print(item)
    print("first default false bad")
    for item in uns_false:
        print(item)

    # Greedy experiment: satisfy every non-binary residual positive tail by
    # setting its first unassigned literal true, then run BCP again.
    for ci, _, cl in uns_false:
        pass
    q = deque()
    picked = 0
    for ci, cl in enumerate(clauses):
        sat = False
        un_lits = []
        for lit in cl:
            st = status(lit, asg)
            if st is True:
                sat = True
                break
            if st is None:
                un_lits.append(lit)
        if not sat and len(un_lits) > 2:
            lit = un_lits[0]
            v = abs(lit)
            want = lit > 0
            if asg[v] is None:
                asg[v] = want
                picked += 1
                for cj in occ[v]:
                    q.append(cj)
    contra2 = False
    while q:
        ci = q.popleft()
        sat = False
        un = []
        for lit in clauses[ci]:
            st = status(lit, asg)
            if st is True:
                sat = True
                break
            if st is None:
                un.append(lit)
        if sat:
            continue
        if not un:
            contra2 = True
            print("greedy contradiction", ci, len(clauses[ci]), clauses[ci])
            break
        if len(un) == 1:
            lit = un[0]
            v = abs(lit)
            want = lit > 0
            if asg[v] is not None and asg[v] != want:
                contra2 = True
                print("greedy assign conflict", v)
                break
            if asg[v] is None:
                asg[v] = want
                for cj in occ[v]:
                    q.append(cj)
    rem2 = Counter()
    for cl in clauses:
        sat = False
        un = 0
        for lit in cl:
            st = status(lit, asg)
            if st is True:
                sat = True
                break
            if st is None:
                un += 1
        if not sat:
            rem2[un] += 1
    print("greedy picked", picked, "assigned", sum(x is not None for x in asg), "contra", contra2, "remaining", sorted(rem2.items()))


if __name__ == "__main__":
    main(sys.argv[1])
