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

def read(path):
  op=lzma.open if path.endswith('.xz') else open
  clauses=[]; n=0
  with op(path,'rt') as f:
    for line in f:
      if line.startswith(('c','p')):
        if line.startswith('p'): n=int(line.split()[2])
        continue
      if line.strip(): clauses.append(tuple(map(int,line.split()[:-1])))
  return n,clauses

def parse(cla):
  unit=[c[0] for c in cla if len(c)==1]
  init_and=abs(unit[0])
  # state starts 1, size 130, state1 start is first var > init_and with many xnor target? fixed infer: min var in xnor after init_and +1? here 158
  state0=1
  state_size=130
  state1=init_and+1
  # init constants from equality aux implied by unit AND
  init=[0]*state_size
  # find children of init_and big clause or binaries -init_and child
  children=[c[1] for c in cla if len(c)==2 and c[0]==-init_and and c[1]>0]
  # but clauses are -157 child; parse each child eq constants
  for aux in children:
    for c in cla:
      if len(c)==2 and c[0]==-aux and 1<=abs(c[1])<=state_size:
        init[abs(c[1])-1]=1 if c[1]>0 else 0
  # xnor defs
  byz=collections.defaultdict(list)
  for c in cla:
    if len(c)==3: byz[abs(c[0])].append(c)
  xnor={}
  for z,cs in byz.items():
    if len(cs)==4:
      vs={abs(l) for c in cs for l in c if abs(l)!=z}
      if len(vs)==2: xnor[z]=tuple(sorted(vs))
  ands={}
  for c in cla:
    if len(c)==6 and c[0]>0 and all(x<0 for x in c[1:]): ands[c[0]]=[-x for x in c[1:]]
  # first dir aux are four vars after action bits? detect first four 2-bit decoders at state1+130
  dirs=list(range(state1+130+2, state1+130+6)) # 290..293
  moves=[]
  for d in dirs:
    pre=[]; mapping=[None]*state_size
    for c in cla:
      if len(c)==2 and c[0]==-d:
        if c[1]<0 and abs(c[1]) < state1: pre.append(abs(c[1])-1)
        if c[1]>0 and c[1] in ands:
          for z in ands[c[1]]:
            a,b=xnor[z]
            if state1 <= a < state1+state_size and 1 <= b <= state_size: old,new=b,a
            elif state1 <= b < state1+state_size and 1 <= a <= state_size: old,new=a,b
            else: continue
            mapping[new-state1]=old-1
    if all(x is not None for x in mapping): moves.append((pre,mapping))
  # goal: final OR last clause long positive; use last positive 27/long and choose first goal indicator? Need goal constants from one OR child nearest n
  last=cla[-1]
  goal_aux=max(last)
  goal_children=[c[1] for c in cla if len(c)==2 and c[0]==-goal_aux and c[1]>0]
  goal=[0]*state_size
  # goal children may be eq aux for last checked state; parse constants and normalize to state offset minbit
  pairs=[]
  for aux in goal_children:
    bits=[]; vals=[]
    for c in cla:
      if len(c)==2 and c[0]==-aux:
        bits.append(abs(c[1])); vals.append(1 if c[1]>0 else 0)
    if bits:
      pairs += list(zip(bits,vals))
  mn=min(b for b,v in pairs)
  for b,v in pairs:
    if 0 <= b-mn < state_size: goal[b-mn]=v
  return tuple(init), tuple(goal), moves

def apply(s, mv):
  pre,map_=mv
  for i in pre:
    if s[i]: return None
  return tuple(s[j] for j in map_)

def hamming(s,g): return sum(a!=b for a,b in zip(s,g))//20

def astar(init,goal,moves,limit=80,maxt=30):
  start=time.time(); pq=[]; counter=0
  heapq.heappush(pq,(hamming(init,goal),0,counter,init,()))
  best={init:0}; exp=0
  while pq and time.time()-start<maxt:
    f,g,_,s,path=heapq.heappop(pq)
    if s==goal: return path,exp
    if g!=best.get(s): continue
    exp+=1
    if g>=limit: continue
    for mi,mv in enumerate(moves):
      ns=apply(s,mv)
      if ns is None: continue
      ng=g+1
      if ng<best.get(ns,999):
        best[ns]=ng; counter+=1
        heapq.heappush(pq,(ng+hamming(ns,goal),ng,counter,ns,path+(mi,)))
  return None,exp

if __name__=='__main__':
 n,cla=read(sys.argv[1]); init,goal,moves=parse(cla)
 print('moves',len(moves),'init ones',sum(init),'goal ones',sum(goal), 'h0',hamming(init,goal))
 p,e=astar(init,goal,moves,maxt=float(sys.argv[2]) if len(sys.argv)>2 else 10)
 print('res', None if p is None else len(p), p, 'exp', e)
