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

def read_dimacs(path):
    clauses=[]; nvars=0
    with open(path) as f:
        toks=[]
        for line in f:
            if not line.strip() or line.startswith('c'): continue
            if line.startswith('p'):
                parts=line.split(); nvars=int(parts[2]); continue
            toks.extend(map(int,line.split()))
        cur=[]
        for x in toks:
            if x==0:
                clauses.append(cur); cur=[]
            else: cur.append(x)
    return nvars, clauses

def add_pol(out, expr, next_id):
    out.append('pol '+expr+' ;')
    return next_id+1, next_id

def add_rup(out, con, next_id):
    out.append('rup '+con+' ;')
    return next_id+1, next_id

def plus(ids):
    ids=[str(x) for x in ids]
    if not ids: raise ValueError('empty ids')
    expr=ids[0]
    for x in ids[1:]: expr += ' '+x+' +'
    return expr

def clause_maps(nvars, clauses):
    long=[(i+1,c) for i,c in enumerate(clauses) if len(c)>2]
    n=round(math.sqrt(len(long)))
    if n*n!=len(long) or nvars % n: raise ValueError('bad n')
    m=nvars//n
    line_id=[[None]*n for _ in range(n)]
    for cid,c in long:
        ys=[None]*m
        for lit in c:
            assert lit>0
            v=lit-1; x=v//n; y=v%n
            assert ys[x] is None
            ys[x]=y
        b=ys[0]
        a=(ys[1]-ys[0])%n if m>1 else 0
        for x,y in enumerate(ys):
            if y != (a*x+b)%n: raise ValueError(('not line',cid,a,b,x,y))
        line_id[a][b]=cid
    bin_id=[[[None]*n for _ in range(n)] for __ in range(m)]
    for cid,c in [(i+1,c) for i,c in enumerate(clauses) if len(c)==2]:
        if c[0]>=0 or c[1]>=0: raise ValueError('bad bin')
        v1=-c[0]-1; v2=-c[1]-1
        x1,y1=divmod(v1,n); x2,y2=divmod(v2,n)
        if x1!=x2: raise ValueError('cross bin')
        if y1>y2: y1,y2=y2,y1
        bin_id[x1][y1][y2]=cid
    return n,m,line_id,bin_id

def gen(cnf, proof):
    nvars, clauses=read_dimacs(cnf)
    n,m,line_id,bin_id=clause_maps(nvars, clauses)
    if m!=n: raise ValueError('not square')
    for x in range(n):
      for y1 in range(n):
        for y2 in range(y1+1,n):
          if bin_id[x][y1][y2] is None: raise ValueError('missing bin')
    out=['pseudo-Boolean proof version 3.0']
    next_id=len(clauses)+1
    # column AMO cardinalities, ids are constraints sum_y ~x_xy >= n-1
    col_amo=[]
    for x in range(n):
        # k=2 starts from original binary for y=0,1
        prev=bin_id[x][0][1]
        for k in range(3,n+1):
            # derive AMO over y=0..k-1. pair clauses y=0..k-2 with new y=k-1
            pair_ids=[bin_id[x][i][k-1] for i in range(k-1)]
            expr=plus(pair_ids)
            expr += f' {prev} {k-2} * + {k-1} d'
            next_id, prev = add_pol(out, expr, next_id)
        col_amo.append(prev)
    # total selected lower bound from all lines: sum vars >= n
    all_line_ids=[line_id[a][b] for a in range(n) for b in range(n)]
    expr=plus(all_line_ids) + f' {n} d'
    next_id,total_lower=add_pol(out, expr, next_id)
    # total upper: sum all col AMOs -> sum all ~vars >= n(n-1)
    next_id,total_upper=add_pol(out, plus(col_amo), next_id)
    # column ALO for col0 and col1
    col_alo=[]
    for c in [0,1]:
        ids=[total_lower]+[col_amo[x] for x in range(n) if x!=c]
        next_id, cid=add_pol(out, plus(ids), next_id)
        col_alo.append(cid)
    # line AMO for every line
    line_amo=[[None]*n for _ in range(n)]
    for a in range(n):
      for b in range(n):
        ids=[line_id[aa][bb] for aa in range(n) for bb in range(n) if not (aa==a and bb==b)]
        expr=f'{total_upper} {n} *'
        for cid in ids:
            expr += f' {cid} +'
        next_id, cid=add_pol(out, expr, next_id)
        line_amo[a][b]=cid
    # cross exclusions between col 0 and col 1. Pair (0,y0),(1,y1) lies on a=y1-y0,b=y0.
    cross=[[None]*n for _ in range(n)]
    for y0 in range(n):
      for y1 in range(n):
        v0=0*n+y0+1; v1=1*n+y1+1
        # line AMO is enough for RUP; emit direct binary.
        next_id,cid=add_rup(out, f'1 ~x{v0} 1 ~x{v1} >= 1', next_id)
        cross[y0][y1]=cid
    # From col1 ALO plus all cross clauses for each y0, derive ~x(0,y0).
    units=[]
    for y0 in range(n):
        ids=[col_alo[1]]+[cross[y0][y1] for y1 in range(n)]
        expr=plus(ids)+f' {n} d'
        next_id,cid=add_pol(out, expr, next_id)
        units.append(cid)
    # Contradict col0 ALO with all ~x0 units.
    next_id,contr=add_pol(out, plus([col_alo[0]]+units), next_id)
    out += ['output NONE ;', f'conclusion UNSAT : {contr} ;', 'end pseudo-Boolean proof ;']
    open(proof,'w').write('\n'.join(out)+'\n')
    print('n',n,'m',m,'clauses',len(clauses),'derived',next_id-len(clauses)-1,'last',contr,'proof lines',len(out), file=sys.stderr)

if __name__=='__main__': gen(sys.argv[1],sys.argv[2])
