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

def read_cnf(path):
    clauses=[]; V=C=0
    with open(path) as fh:
        for line in fh:
            if not line or line[0]=='c': continue
            if line[0]=='p':
                _,_,V,C=line.split()[:4]; V=int(V); C=int(C); continue
            lits=[int(x) for x in line.split() if x!='0']
            if lits: clauses.append(lits)
    return V,clauses

def parse_model(path):
    vals={}
    with open(path) as fh:
        for line in fh:
            if line.startswith('v'):
                for x in line.split()[1:]:
                    l=int(x)
                    if l: vals[abs(l)]=l>0
    return vals

class BCP:
    def __init__(self,V,clauses):
        self.V=V; self.clauses=clauses; self.n=len(clauses)
        self.w0=[0]*self.n; self.w1=[0]*self.n; self.watch=[[] for _ in range(2*V+2)]
        for ci,cl in enumerate(clauses):
            self.w1[ci]=0 if len(cl)==1 else 1
            self.watch[self.wi(cl[0])].append(ci)
            if len(cl)>1: self.watch[self.wi(cl[1])].append(ci)
        self.val=bytearray(V+1); self.trail=[]; self.q=0
    def wi(self,lit):
        v=abs(lit); return 2*(v-1)+(1 if lit>0 else 0)
    def litval(self,lit):
        v=self.val[abs(lit)]
        if not v: return 0
        return 1 if ((v==1)==(lit>0)) else -1
    def enqueue(self,lit):
        v=abs(lit); want=1 if lit>0 else 2
        if self.val[v]: return self.val[v]==want
        self.val[v]=want; self.trail.append(lit); return True
    def propagate(self):
        while self.q < len(self.trail):
            lit=self.trail[self.q]; self.q+=1; fl=-lit; wl=self.watch[self.wi(fl)]; i=0
            while i<len(wl):
                ci=wl[i]; cl=self.clauses[ci]
                if cl[self.w0[ci]]==fl: fw=0; ow=self.w1[ci]
                elif cl[self.w1[ci]]==fl: fw=1; ow=self.w0[ci]
                else:
                    wl[i]=wl[-1]; wl.pop(); continue
                if self.litval(cl[ow])==1:
                    i+=1; continue
                found=False
                for k,l in enumerate(cl):
                    if k==self.w0[ci] or k==self.w1[ci]: continue
                    if self.litval(l)!=-1:
                        if fw==0: self.w0[ci]=k
                        else: self.w1[ci]=k
                        wl[i]=wl[-1]; wl.pop(); self.watch[self.wi(l)].append(ci); found=True; break
                if found: continue
                other=cl[ow]; lv=self.litval(other)
                if lv==-1: return ci
                if lv==0:
                    if not self.enqueue(other): return ci
                i+=1
        return None
    def checkpoint(self): return len(self.trail), self.q
    def undo(self,cp):
        lim,q=cp
        for lit in reversed(self.trail[lim:]): self.val[abs(lit)]=0
        del self.trail[lim:]
        self.q=q

V,clauses=read_cnf(sys.argv[1]); model=parse_model(sys.argv[2])
# infer P from first clause's max auxiliary literal position
P=abs(clauses[0][2])-1
print('V',V,'clauses',len(clauses),'P',P,'model',len(model))
b=BCP(V,clauses)
for v in range(1,P+1):
    lit=v if model.get(v,False) else -v
    if not b.enqueue(lit): print('assumption conflict',v); sys.exit(1)
conf=b.propagate(); print('after primary conf',conf,'assigned',len(b.trail),'unassigned',V-len(b.trail))
if conf is not None: sys.exit(2)
# greedy fill aux with trying false then true, no deep backtracking first
for v in range(P+1,V+1):
    if b.val[v]: continue
    cp=b.checkpoint()
    if b.enqueue(-v) and b.propagate() is None: continue
    b.undo(cp)
    if not b.enqueue(v): print('both conflict enqueue',v); sys.exit(3)
    conf=b.propagate()
    if conf is not None:
        print('both conflict prop',v,conf); sys.exit(4)
print('filled assigned',len(b.trail))
bad=0
for ci,cl in enumerate(clauses):
    if not any(b.litval(l)==1 for l in cl):
        bad+=1
        if bad<10: print('bad',ci+1,cl)
print('bad total',bad)
