#!/usr/bin/env python3
from profile_up import parse
import sys, time, collections


class Solver:
    def __init__(self, n, clauses):
        self.n = n
        self.clauses = [list(c) for c in clauses]
        self.val = [0] * (n + 1)
        self.trail = []
        self.qhead = 0
        self.watches = [[] for _ in range(2 * n + 2)]
        self.wp = []
        self.decisions = []
        self.learned = 0
        self.nodes = 0
        self.conflicts = 0
        self.score = [0] * (n + 1)
        for c in clauses:
            for x in c:
                self.score[abs(x)] += 1
        self.order = sorted(range(1, n + 1), key=lambda v: self.score[v], reverse=True)
        for i, c in enumerate(self.clauses):
            if len(c) == 1:
                self.wp.append((0, 0))
                self.watches[self.idx(c[0])].append(i)
                if not self.enqueue(c[0]):
                    self.root_conflict = True
            else:
                self.wp.append((0, 1))
                self.watches[self.idx(c[0])].append(i)
                self.watches[self.idx(c[1])].append(i)
        self.root_conflict = False

    def idx(self, lit):
        return 2 * abs(lit) + (1 if lit < 0 else 0)

    def lval(self, lit):
        x = self.val[abs(lit)]
        return 0 if x == 0 else (1 if (lit > 0) == (x > 0) else -1)

    def enqueue(self, lit):
        v, w = abs(lit), 1 if lit > 0 else -1
        if self.val[v] == w:
            return True
        if self.val[v] == -w:
            return False
        self.val[v] = w
        self.trail.append(lit)
        return True

    def add_clause(self, c):
        self.learned += 1
        i = len(self.clauses)
        self.clauses.append(list(c))
        if len(c) == 1:
            self.wp.append((0, 0))
            self.watches[self.idx(c[0])].append(i)
            return self.enqueue(c[0])
        self.wp.append((0, 1))
        self.watches[self.idx(c[0])].append(i)
        self.watches[self.idx(c[1])].append(i)
        return True

    def propagate(self):
        while self.qhead < len(self.trail):
            lit = self.trail[self.qhead]
            self.qhead += 1
            ws = self.watches[self.idx(-lit)]
            j = 0
            while j < len(ws):
                ci = ws[j]
                c = self.clauses[ci]
                a, b = self.wp[ci]
                if c[a] != -lit:
                    a, b = b, a
                other = c[b]
                if self.lval(other) == 1:
                    j += 1
                    continue
                moved = False
                for k, x in enumerate(c):
                    if k == a or k == b:
                        continue
                    if self.lval(x) != -1:
                        self.wp[ci] = (k, b)
                        ws[j] = ws[-1]
                        ws.pop()
                        self.watches[self.idx(x)].append(ci)
                        moved = True
                        break
                if moved:
                    continue
                if self.lval(other) == -1:
                    self.conflicts += 1
                    return ci
                if not self.enqueue(other):
                    self.conflicts += 1
                    return ci
                j += 1
        return -1

    def mark(self):
        return len(self.trail), self.qhead, len(self.decisions)

    def backtrack(self, mark):
        tl, qh, dl = mark
        for lit in reversed(self.trail[tl:]):
            self.val[abs(lit)] = 0
        del self.trail[tl:]
        self.qhead = min(qh, len(self.trail))
        del self.decisions[dl:]

    def choose(self):
        for v in self.order:
            if self.val[v] == 0:
                return v
        return 0

    def all_sat(self):
        for c in self.clauses:
            if not any(self.lval(x) == 1 for x in c):
                return False
        return True

    def learn_current(self):
        c = [-d for d in self.decisions]
        if c:
            self.add_clause(c)
        return c

    def dfs(self, limit, deadline):
        self.nodes += 1
        if self.nodes > limit or time.time() > deadline:
            return None
        if self.propagate() >= 0:
            self.learn_current()
            return False
        v = self.choose()
        if v == 0:
            return True if self.all_sat() else None
        # Try polarity that currently appears more often in binary/ternary constraints is ignored for now.
        saw_unknown = False
        for lit in (v, -v):
            m = self.mark()
            self.decisions.append(lit)
            if self.enqueue(lit):
                r = self.dfs(limit, deadline)
                if r is True:
                    return True
                if r is None:
                    saw_unknown = True
            else:
                r = False
            self.backtrack(m)
        if saw_unknown:
            return None
        # Both children failed under added learned clauses; propagate parent and learn parent clause.
        if self.propagate() >= 0:
            self.learn_current()
        return False


def main():
    for p in sys.argv[1:]:
        n, clauses, sizes = parse(p)
        s = Solver(n, clauses)
        t = time.time()
        r = s.dfs(20000, t + 60)
        print(p, "res", r, "nodes", s.nodes, "conf", s.conflicts, "learned", s.learned, "trail", len(s.trail), "sec", time.time()-t)


if __name__ == "__main__":
    main()
