#!/usr/bin/env python3
import sys, collections, itertools, time
cnf=sys.argv[1]; model=sys.argv[2]
vals={}
for line in open(model):
    if line.startswith('v'):
        for x in line.split()[1:]:
            l=int(x)
            if l: vals[abs(l)]=l>0
clauses=[]; V=0
for line in open(cnf):
    if not line or line[0]=='c': continue
    if line[0]=='p': V=int(line.split()[2]); continue
    cl=[int(x) for x in line.split() if x!='0']
    if cl: clauses.append(cl)

def sat_lit(l,assign): return assign.get(abs(l), vals.get(abs(l),False)) == (l>0)
def unsats(assign=vals):
    bad=[]
    for i,cl in enumerate(clauses):
        if not any((l>0 and assign.get(abs(l),False)) or (l<0 and not assign.get(abs(l),False)) for l in cl): bad.append(i)
    return bad
bad=unsats(vals); print('initial bad',len(bad))
S=set(abs(l) for i in bad for l in clauses[i])
# occurrence lists
occ=collections.defaultdict(list)
for i,cl in enumerate(clauses):
    for l in cl: occ[abs(l)].append(i)
for round in range(6):
    rel=set()
    for v in S: rel.update(occ[v])
    sub=[]; empty=False
    for ci in rel:
        cl=clauses[ci]
        # if satisfied by fixed outside S, ignore
        ok=False; subcl=[]
        for l in cl:
            v=abs(l)
            if v in S: subcl.append(l)
            elif (l>0 and vals[v]) or (l<0 and not vals[v]): ok=True; break
        if ok: continue
        if not subcl:
            empty=True; break
        sub.append(subcl)
    print('round',round,'vars',len(S),'rel',len(rel),'sub clauses',len(sub),'empty',empty)
    if len(S)>300: break
    # DPLL with simple unit propagation
    varlist=list(S); idx={v:i for i,v in enumerate(varlist)}
    # convert to local lits
    loc=[]
    for cl in sub:
        loc.append([ (idx[abs(l)]+1) * (1 if l>0 else -1) for l in cl])
    assign=[0]*(len(varlist)+1)
    # initial phase from vals
    phase=[False]+[vals[v] for v in varlist]
    def prop(stack):
        changed=True
        while changed:
            changed=False
            for cl in loc:
                sat=False; un=0; last=0
                for l in cl:
                    a=assign[abs(l)]
                    if a==0: un+=1; last=l
                    elif (a==1)==(l>0): sat=True; break
                if sat: continue
                if un==0: return False
                if un==1:
                    v=abs(last); val=1 if last>0 else -1
                    if assign[v] and assign[v]!=val: return False
                    if assign[v]==0:
                        assign[v]=val; stack.append(v); changed=True
        return True
    # order by occurrence
    order=sorted(range(1,len(varlist)+1), key=lambda x: -sum(1 for cl in loc for l in cl if abs(l)==x))
    nodes=0; sol=None
    def dfs(pos=0):
        nonlocal_nodes=None
        global_dummy=0
        # find next unassigned
        v=0
        for x in order:
            if assign[x]==0: v=x; break
        if v==0: return True
        # try current phase then opposite
        for b in [phase[v], not phase[v]]:
            stack=[]; assign[v]=1 if b else -1; stack.append(v)
            if prop(stack) and dfs(): return True
            for u in reversed(stack): assign[u]=0
        return False
    st=[]
    ok=prop(st) and dfs()
    print(' dpll',ok)
    if ok:
        new=vals.copy()
        for i,v in enumerate(varlist,1):
            if assign[i]!=0: new[v]=assign[i]>0
        bad2=unsats(new); print(' repaired bad',len(bad2))
        if not bad2:
            with open('/workspace/repaired.model','w') as out:
                out.write('s SATISFIABLE\nv ')
                for v in range(1,V+1): out.write(str(v if new.get(v,False) else -v)+' ')
                out.write('0\n')
            sys.exit(0)
        S.update(abs(l) for i in bad2 for l in clauses[i])
    else:
        # expand one hop from clauses
        for ci in list(rel)[:10000]:
            for l in clauses[ci]: S.add(abs(l))
print('failed')
