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

def read_cnf(path):
    op=lzma.open if path.endswith('.xz') else open
    with op(path,'rt') as fh:
        V=C=None; P=None; clauses=[]; pos_clauses=[]; units=[]
        for line in fh:
            if not line or line[0]=='c': continue
            if line[0]=='p':
                parts=line.split(); V=int(parts[2]); C=int(parts[3]); continue
            lits=[int(x) for x in line.split() if x!='0']
            if not lits: continue
            idx=len(clauses); clauses.append(lits)
            if len(lits)==1: units.append(lits[0])
            # family primary variables end immediately before first auxiliary in the first clause
            if idx==0: P=abs(lits[2])-1
            if all(l>0 and abs(l)<=P for l in lits): pos_clauses.append(idx)
    return V,clauses,units,pos_clauses

def solve(V,clauses,units,pos_clauses):
    n=len(clauses)
    watches=[[] for _ in range(2*V+2)]
    w0=[0]*n; w1=[0]*n
    def idx(lit): return lit+V if lit>0 else V-lit  # wrong? unique: positive V+lit, negative V-lit
    # actually lit=-1 -> V+1, lit=1 -> V+1 collision. use 2*(v-1)+sign
    def wi(lit):
        v=abs(lit); return 2*(v-1)+(0 if lit<0 else 1)
    for ci,cl in enumerate(clauses):
        w0[ci]=0; w1[ci]=0 if len(cl)==1 else 1
        watches[wi(cl[w0[ci]])].append(ci)
        if len(cl)>1: watches[wi(cl[w1[ci]])].append(ci)
    val=bytearray(V+1) # 0 un, 1 true, 2 false
    trail=[]; q=0
    def lit_val(lit):
        v=val[abs(lit)]
        if v==0: return 0
        truth=(v==1)
        return 1 if (truth==(lit>0)) else -1
    def enqueue(lit):
        v=abs(lit); want=1 if lit>0 else 2
        if val[v]: return val[v]==want
        val[v]=want; trail.append(lit); return True
    def propagate():
        nonlocal q
        while q<len(trail):
            lit=trail[q]; q+=1; false_lit=-lit; wl=watches[wi(false_lit)]; i=0
            while i<len(wl):
                ci=wl[i]; cl=clauses[ci]
                # determine which watch is false_lit
                if cl[w0[ci]]==false_lit:
                    fw=0; ow=w1[ci]
                elif cl[w1[ci]]==false_lit:
                    fw=1; ow=w0[ci]
                else:
                    # stale; remove
                    wl[i]=wl[-1]; wl.pop(); continue
                if lit_val(cl[ow])==1:
                    i+=1; continue
                # find replacement
                found=False
                for k,l in enumerate(cl):
                    if k==w0[ci] or k==w1[ci]: continue
                    if lit_val(l)!=-1:
                        if fw==0: w0[ci]=k
                        else: w1[ci]=k
                        wl[i]=wl[-1]; wl.pop(); watches[wi(l)].append(ci); found=True; break
                if found: continue
                # unit or conflict
                other=cl[ow]
                lv=lit_val(other)
                if lv==-1:
                    return ci
                if lv==0:
                    if not enqueue(other): return ci
                i+=1
        return None
    for u in units:
        if not enqueue(u): return None, 'unit_conflict'
    c=propagate()
    if c is not None: return None, ('conflict',c)
    decisions=0
    # choose first unassigned from each unsatisfied all-positive clause, loop until fixed
    changed=True
    while True:
        made=False
        for ci in pos_clauses:
            cl=clauses[ci]
            sat=False; first_un=0
            for l in cl:
                lv=lit_val(l)
                if lv==1: sat=True; break
                if lv==0 and first_un==0: first_un=l
            if sat: continue
            if first_un==0:
                return None, ('pos_conflict',ci,cl)
            decisions+=1
            if not enqueue(first_un): return None,('dec_conflict',ci,first_un)
            c=propagate()
            if c is not None: return None,('conflict',c,'after',first_un,'decisions',decisions)
            made=True
        if not made: break
    # assign remaining unassigned false initially
    for v in range(1,V+1):
        if val[v]==0:
            enqueue(-v)
            c=propagate()
            if c is not None:
                # try true? no backtrack currently
                return None,('fill_conflict',c,v,'decisions',decisions)
    # verify
    bad=0
    for ci,cl in enumerate(clauses):
        if not any(lit_val(l)==1 for l in cl):
            bad+=1
            if bad<5: print('bad',ci+1,cl)
    return val, ('ok' if bad==0 else ('bad',bad),'decisions',decisions)

if __name__=='__main__':
    t=time.time(); V,clauses,units,pos=read_cnf(sys.argv[1]); print('read',V,len(clauses),'units',len(units),'pos',len(pos),'time',time.time()-t)
    t=time.time(); val,status=solve(V,clauses,units,pos); print('status',status,'time',time.time()-t)
