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


def main():
    path, cnf, pbp = sys.argv[1:4]
    open(cnf, "wb").write(lzma.open(path, "rb").read())

    nclauses = 0
    clauses = []
    units = []
    for line in open(cnf):
        if line.startswith("p"):
            nclauses = int(line.split()[3])
        elif not line.startswith("c"):
            lits = [int(x) for x in line.split() if x != "0"]
            if lits:
                clauses.append(lits)
                if len(lits) == 1:
                    units.append(lits[0])

    m = max(len(c) for c in clauses)
    cur = nclauses
    lines = ["pseudo-Boolean proof version 3.0\n"]

    def add(line):
        nonlocal cur
        lines.append(line + "\n")
        cur += 1
        return cur

    cell_amo = []
    for cell in range(m):
        base = cell * m
        pair = [[0] * (m + 1) for _ in range(m + 1)]
        for j in range(2, m + 1):
            for i in range(1, j):
                pair[i][j] = add(f"rup 1 ~x{base+i} 1 ~x{base+j} >= 1 ;")
        cprev = pair[1][2]
        for k in range(3, m + 1):
            toks = [str(pair[1][k])]
            for i in range(2, k):
                toks += [str(pair[i][k]), "+"]
            bid = add("pol " + " ".join(toks) + " ;")
            cid = add(f"pol {bid} {cprev} {k-2} * + {k-1} d ;")
            cprev = cid
        cell_amo.append(cprev)

    occ = []
    for value in range(1, m + 1):
        occ.append(add("rup " + " ".join(f"1 x{cell*m+value}" for cell in range(m)) + " >= 1 ;"))

    toks = [str(cell_amo[0])]
    for cid in cell_amo[1:]:
        toks += [str(cid), "+"]
    total = add("pol " + " ".join(toks) + " ;")

    for value in range(1, m + 1):
        toks = [str(total)]
        for other in range(1, m + 1):
            if other != value:
                toks += [str(occ[other - 1]), "+"]
        add("pol " + " ".join(toks) + " ;")

    n = int(m ** 0.5 + 0.5)
    neigh = []
    for cell in range(m):
        r, c = divmod(cell, n)
        ns = []
        for dr in (-1, 0, 1):
            for dc in (-1, 0, 1):
                if dr == 0 and dc == 0:
                    continue
                rr, cc = r + dr, c + dc
                if 0 <= rr < n and 0 <= cc < n:
                    ns.append(rr * n + cc)
        neigh.append(ns)

    all_cells = (1 << m) - 1
    dom = [0] + [all_cells for _ in range(m)]
    original_pos = set(units)
    known_pos = set(units)
    known_neg = set()

    def var(cell, value):
        return cell * m + value

    def emit_pos(lit):
        if lit in known_pos:
            return
        known_pos.add(lit)
        add(f"rup 1 x{lit} >= 1 ;")

    def emit_neg(lit):
        if lit in known_neg:
            return
        known_neg.add(lit)
        add(f"rup 1 ~x{lit} >= 1 ;")

    for unit in units:
        cell = (unit - 1) // m
        value = (unit - 1) % m + 1
        dom[value] = 1 << cell

    reason = "fixed point"
    while True:
        changed = False
        singleton_cell = {}
        for value in range(1, m + 1):
            if dom[value] == 0:
                reason = f"empty value {value}"
                changed = False
                break
            if dom[value] & (dom[value] - 1) == 0:
                cell = dom[value].bit_length() - 1
                emit_pos(var(cell, value))
                if cell in singleton_cell:
                    reason = f"duplicate cell {cell}"
                    changed = False
                    break
                singleton_cell[cell] = value
        else:
            occupied = 0
            for cell in singleton_cell:
                occupied |= 1 << cell
            for value in range(1, m + 1):
                if dom[value] & (dom[value] - 1):
                    removed = dom[value] & occupied
                    x = removed
                    while x:
                        b = x & -x
                        cell = b.bit_length() - 1
                        emit_neg(var(cell, value))
                        x -= b
                    nd = dom[value] & ~occupied
                    if nd != dom[value]:
                        dom[value] = nd
                        changed = True

            for value in range(1, m + 1):
                x = dom[value]
                while x:
                    b = x & -x
                    cell = b.bit_length() - 1
                    ok = True
                    if value > 1 and not any((dom[value - 1] >> nb) & 1 for nb in neigh[cell]):
                        ok = False
                    if value < m and not any((dom[value + 1] >> nb) & 1 for nb in neigh[cell]):
                        ok = False
                    if not ok:
                        emit_neg(var(cell, value))
                        dom[value] &= ~b
                        changed = True
                    x -= b
            if changed:
                continue
            reason = "fixed point"
        break

    add("rup >= 1 ;")

    lines += ["output NONE ;\n", "conclusion UNSAT : -1 ;\n", "end pseudo-Boolean proof ;\n"]
    open(pbp, "w").writelines(lines)
    print(f"m={m} constraints={cur} units={units} reason={reason}", file=sys.stderr)


if __name__ == "__main__":
    main()
