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

from greedy_semantic import BCP, detect_blocks, read

sys.setrecursionlimit(1000000)


def solve(path, seconds=30):
    t0 = time.time()
    V, P, clauses = read(path)
    blocks = detect_blocks(clauses, P)
    pos = [ci for ci, cl in enumerate(clauses) if all(l > 0 for l in cl)]
    print("read", V, len(clauses), "P", P, "blocks", len(blocks), "pos", len(pos), "time", time.time() - t0)
    b = BCP(V, clauses)

    def checkpoint():
        return len(b.trail), b.q

    def undo(cp):
        lim, q = cp
        for lit in reversed(b.trail[lim:]):
            b.val[abs(lit)] = 0
        del b.trail[lim:]
        b.q = q

    def enqueue_all(lits):
        for lit in lits:
            if not b.enqueue(lit):
                return False
        return b.propagate() is None

    init = []
    for cl in clauses:
        if len(cl) == 1:
            init.append(cl[0])
    for _x0, y0, _k, sels, bitslist in blocks:
        init.append(sels[0])
        init.extend(-s for s in sels[1:])
        init.extend(-(y0 + i) for i in range(10))
        init.extend(-bit for bit in bitslist[0])
        for bits in bitslist[1:]:
            init.extend(bits)
    if not enqueue_all(init):
        print("initial conflict")
        return None
    print("after init trail", len(b.trail), "time", time.time() - t0)

    # Static occurrence score: prefer literals that satisfy many positive clauses and are less constrained by negatives.
    pos_occ = [0] * (V + 1)
    neg_occ = [0] * (V + 1)
    for cl in clauses:
        for l in cl:
            if l > 0:
                pos_occ[l] += 1
            else:
                neg_occ[-l] += 1

    nodes = 0
    best_trail = len(b.trail)

    def choose_pos_clause():
        best = None
        best_un = None
        for ci in pos:
            cl = clauses[ci]
            sat = False
            un = []
            for l in cl:
                lv = b.lit_val(l)
                if lv == 1:
                    sat = True
                    break
                if lv == 0:
                    un.append(l)
            if sat:
                continue
            if not un:
                return ci, []
            if best is None or len(un) < len(best_un):
                best = ci
                best_un = un
                if len(un) == 1:
                    break
        return best, best_un

    def dfs(depth=0):
        nonlocal nodes, best_trail
        if time.time() - t0 > seconds:
            return False
        nodes += 1
        if len(b.trail) > best_trail:
            best_trail = len(b.trail)
            if nodes % 100 == 0:
                print("progress nodes", nodes, "depth", depth, "trail", best_trail, "time", time.time() - t0)
        ci, un = choose_pos_clause()
        if ci is None:
            print("all positive satisfied", "nodes", nodes, "trail", len(b.trail), "time", time.time() - t0)
            return True
        if not un:
            return False
        # Try literals with fewer negative occurrences first; this tends to avoid violating all-negative range clauses.
        cand = sorted(un, key=lambda l: (neg_occ[l] - pos_occ[l], l))
        # For long clauses, try a small representative prefix before the rest. This keeps early failures cheap.
        for lit in cand:
            cp = checkpoint()
            if b.enqueue(lit) and b.propagate() is None and dfs(depth + 1):
                return True
            undo(cp)
        return False

    ok = dfs()
    print("dfs", ok, "nodes", nodes, "trail", len(b.trail), "time", time.time() - t0)
    if not ok:
        return None
    # Greedily complete the remaining variables. This part is not backtracking in the analysis script.
    for v in range(1, V + 1):
        if b.val[v]:
            continue
        cp = checkpoint()
        if b.enqueue(-v) and b.propagate() is None:
            continue
        undo(cp)
        if not b.enqueue(v) or b.propagate() is not None:
            print("completion conflict", v)
            return None
    bad = 0
    for ci, cl in enumerate(clauses):
        if not any(b.lit_val(l) == 1 for l in cl):
            print("bad", ci + 1, cl)
            bad += 1
            break
    print("bad", bad, "assigned", len(b.trail), "time", time.time() - t0)
    return b.val if bad == 0 else None


if __name__ == "__main__":
    solve(sys.argv[1], float(sys.argv[2]) if len(sys.argv) > 2 else 30)
