#!/usr/bin/env python3
import sys


def sat_clause(clause, vals):
    unk = False
    for lit in clause:
        v = abs(lit)
        if v not in vals:
            unk = True
            continue
        b = vals[v]
        if lit < 0:
            b = not b
        if b:
            return True
    return None if unk else False


def apply_group(clauses, vars_, vals):
    unknown = [v for v in vars_ if v not in vals]
    if not unknown or len(unknown) > 8:
        return 0
    if len(unknown) == 1:
        v = unknown[0]
        okvals = []
        for bit in (False, True):
            vals[v] = bit
            if all(sat_clause(c, vals) is True for c in clauses):
                okvals.append(bit)
        del vals[v]
        if not okvals:
            return -1
        if len(okvals) == 1:
            vals[v] = okvals[0]
            return 1
        return 0
    sols = []
    for mask in range(1 << len(unknown)):
        for i, v in enumerate(unknown):
            vals[v] = bool((mask >> i) & 1)
        ok = True
        for c in clauses:
            if sat_clause(c, vals) is not True:
                ok = False
                break
        for v in unknown:
            del vals[v]
        if ok:
            sols.append(mask)
    if not sols:
        return -1
    made = 0
    for i, v in enumerate(unknown):
        bit0 = bool(sols[0] & (1 << i))
        if all(bool(m & (1 << i)) == bit0 for m in sols):
            vals[v] = bit0
            made += 1
    return made


def eval_stream(path, vals, skip_unit_from=10**18):
    nvars = nclauses = None
    cur_key = None
    cur = []
    made_total = 0

    def flush():
        nonlocal made_total, cur_key, cur
        if not cur:
            return True
        key = cur_key
        clauses = cur
        cur = []
        if len(key) == 1:
            lit = clauses[0][0]
            vals[abs(lit)] = lit > 0
            made_total += 1
            return True
        made = apply_group(clauses, key, vals)
        if made < 0:
            print("conflict group", key, clauses[:4])
            return False
        made_total += made
        return True

    with open(path) as f:
        for line in f:
            if not line or line[0] == "c":
                continue
            if line[0] == "p":
                p = line.split()
                nvars, nclauses = int(p[2]), int(p[3])
                continue
            clause = [int(x) for x in line.split() if x != "0"]
            if len(clause) == 1 and abs(clause[0]) >= skip_unit_from:
                continue
            key = tuple(sorted(abs(x) for x in clause))
            if key != cur_key:
                if not flush():
                    return nvars, made_total
                cur_key = key
                cur = [clause]
            else:
                cur.append(clause)
    flush()
    return nvars, made_total


def main():
    path = sys.argv[1]
    w = int(sys.argv[2])
    vals = {1: False}
    x, y, z = [int(a) for a in sys.argv[3:6]]
    for off, value in [(2, x), (2 + w, y), (2 + 2*w, z)]:
        if value < 0:
            value = (1 << w) + value
        for i in range(w):
            vals[off + i] = bool((value >> i) & 1)

    # Skip only the final target/output unit block. Earlier constants remain active.
    nvars, made_total = eval_stream(path, vals, skip_unit_from=(10**18 if len(sys.argv) < 7 else int(sys.argv[6])))
    print("assigned", len(vals), "made", made_total)
    for start in [nvars - 4*w + 2, nvars - 3*w + 2, nvars - 2*w + 1]:
        bits = [vals.get(start + i) for i in range(w)]
        known = sum(b is not None for b in bits)
        val = sum((1 << i) for i, b in enumerate(bits) if b)
        print("range", start, start+w-1, "known", known, "value", val, "lowbits", ''.join('1' if b else '0' if b is False else '?' for b in bits[:64]))


if __name__ == "__main__":
    main()
