import golly as g
import glife, os
from itertools import product
def HaloCoords(clist):
    halo = set([])
    for i in range(0,len(clist), 2):
        for nb in product([-1,0,1],[-1,0,1]):
            halo.add((nb[0]+clist[i], nb[1]+clist[i+1]))
    return halo - CoordsInClist(clist)

def CoordsInClist(clist):
    return set([tuple(clist[i:i+2]) for i in range(0, len(clist), 2)])

def SubpatPresent(subpatClist):
    coords = CoordsInClist(g.getcells(g.getrect()))
    return CoordsInClist(subpatClist).issubset(coords) and \
                        HaloCoords(subpatClist).isdisjoint(coords)

def ClearIfNonempty():
    if len(g.getcells(g.getrect())) != 0:
        g.select(g.getrect())
        g.clear(0)

def RLE(clist):
    g.store(clist, 'temp.rle')
    with open('temp.rle', 'r') as f:
        rle = ''
        for line in f:
            if line.startswith('x ='):
                data = line.split(', ')
                w = int(data[0].removeprefix('x = '))
                h = int(data[1].removeprefix('y = '))
            if not (line.startswith('#') or line.startswith('x =')):
                rle += line.rstrip()
    os.remove('temp.rle')
    return rle

def MinCoords(clist):
    return (min(clist[::2]), min(clist[1::2]))

hf_producing_baits = [ ('2o$2o', 3, -1), ('bo$obo$obo$bo', 1, -1), ('b2o$obo$bo', -3, 1),
                      ('b2o$obo$bo', -3, 2), ('2bo$bobo$o2bo$b2o', -3, -2),
                      ('b2o$o2bo$obo$bo', -3, 4), ('b2o$o2bo$bobo$2bo', 0, -5),
                      ('b2o$o2bo$bobo$2bo', -2,0), ('2ob2o$2ob2o', -3,1),
                      ('2ob2o$2ob2o', -2, -1), ('bo$obo$obo$bo', -1, -2),
                      ('bo$obo$o2bo$b2o', 1, -4)]
hf_producing_catalysts = [('',0,0) for _ in range(len(hf_producing_baits)-2)] + \
                          [('2o$2o', 5,-6), ('2o$2o', 7, -8)]
hf_producing_gliders = [('bo$2bo$3o', -2,-2),('bo$2bo$3o', -2,-4),('obo$b2o$bo', -1, -3),
                        ('obo$b2o$bo', -4, -2), ('bo$2bo$3o', -3, -7),
                        ('bo$2bo$3o', -1,-1), ('2bo$obo$b2o', -4, -7),
                        ('o$b2o$2o', -6,0), ('o$b2o$2o', -3,-4),
                        ('2bo$obo$b2o', -5,-5), ('2bo$obo$b2o', 0, -6),
                        ('2bo$obo$b2o', 2, -8)]
hf_ash = ('6bo$5bobo$5bobo$6bo2$b2o7b2o$o2bo5bo2bo$b2o7b2o2$6bo$5bobo$5bobo$6bo!', -6,-6)
hf_ash_coords = CoordsInClist(g.parse(*hf_ash))
hf_circle = ('2b3o$bo3bo$o5bo$o5bo$o5bo$bo3bo$2b3o!', -3, -3)
hf_circle_coords = CoordsInClist(g.parse(*hf_circle))
hf_circle_halo_coords = HaloCoords(g.parse(*hf_circle))
# okay so these lists work.
fishhook = ('2b2o$bobo$bo$2o', -8,2)
chucklebait = ('4b2o$2bo2bo$bob2o$2bo2b3o$3b2o3bo$6b2o$3b2obo$3bo2bo$4b2o2$2o$o$b3o$3bo!',4,0)
gliderCats = [fishhook, chucklebait]
# last number here is generations after the circular honeyfarm.
fishhookGlider = (('bo$o$3o!',-3,6),20)
chucklebaitGlider = (('bo$o$3o!',-1,7), 28)
glidersEmitted = [fishhookGlider[0], chucklebaitGlider[0]]
timesEmitted = [fishhookGlider[1], chucklebaitGlider[1]]
# compute when the honeyfarm reaches its circular stage
# for each bait.
circularHfGens = []
g.setstep(0)

CHECK_REACTIONS = True
if CHECK_REACTIONS:
    assert(len(hf_producing_gliders) == len(hf_producing_baits) == len(hf_producing_catalysts))
    g.autoupdate = True
    for i in range(len(hf_producing_baits)):
        ClearIfNonempty()
        for sublist in [hf_producing_baits, hf_producing_catalysts, hf_producing_gliders]:
            g.putcells(g.parse(*sublist[i]))
        for _ in range(100):
            g.step()
        clist = g.getcells(g.getrect())
        coords = CoordsInClist(clist)
        blockCoords = CoordsInClist(g.parse(*hf_producing_catalysts[i]))
        if hf_ash_coords.union(blockCoords) != coords:
            g.warn(str(i))
        assert(hf_ash_coords.union(blockCoords) == coords)
        ClearIfNonempty()
    for indCat, gliderCat in enumerate(gliderCats):
        g.putcells(g.parse(*gliderCat))
        g.putcells(g.parse(*hf_circle))
        for _ in range(timesEmitted[indCat]):
            g.step()
        assert(SubpatPresent(g.parse(*glidersEmitted[indCat])))
        assert(SubpatPresent(g.parse(*gliderCat)))
        ClearIfNonempty()

for i in range(len(hf_producing_baits)):
    ClearIfNonempty()
    for sublist in [hf_producing_baits, hf_producing_catalysts, hf_producing_gliders]:
        g.putcells(g.parse(*sublist[i]))
    for t in range(100):
        if SubpatPresent(g.parse(*hf_circle)):
            circularHfGens.append(t)
            # g.warn(f"honey farm found for bait {i} at gen {t}")
            break
        g.step()
    assert(len(circularHfGens) == i+1)
    ClearIfNonempty()
tfns = [glife.identity, glife.swap_xy, glife.flip, glife.swap_xy_flip, glife.flip_x, glife.rcw, glife.flip_y,  glife.rccw]
glider_dirs = [(1,1), (1,1), (-1,-1), (-1,-1), (-1, 1), (-1,1), (1,-1), (1,-1)]
worked = {}
for indCat, gliderCat in enumerate(gliderCats):
    for indBait, bait in enumerate(hf_producing_baits):
        addedCats =  hf_producing_catalysts[indBait]
        inputGlider = hf_producing_gliders[indBait]
        for indTfn, tfn in enumerate(tfns):
            
            ClearIfNonempty()
            tfndBait = g.transform(g.parse(*bait),0,0,*tfn)
            tfndAddedCat = g.transform(g.parse(*hf_producing_catalysts[indBait]),0,0,*tfn)
            tfndInputGlider = g.transform(g.parse(*hf_producing_gliders[indBait]),0,0,*tfn)
            checkHfOccurs = False
            if checkHfOccurs:
                g.putcells(tfndBait)
                g.putcells(tfndAddedCat)
                g.putcells(tfndInputGlider)
                for _ in range(circularHfGens[indBait]):
                    g.step()
                assert(SubpatPresent(g.parse(*hf_circle)))
                ClearIfNonempty()

            # check #0: is transformed bait + additional catalysts + glider-releasing catalysts stable? 
            g.putcells(tfndBait)
            g.putcells(tfndAddedCat)
            g.putcells(g.parse(*gliderCat))
            
            preStepCoords = CoordsInClist(g.getcells(g.getrect()))
            g.step()
            postStepCoords = CoordsInClist(g.getcells(g.getrect()))
            if postStepCoords != preStepCoords:
                worked[(indCat, indTfn, indBait)] = False
                continue

            # check #1: does circular hf appear at the expected gen?
            ClearIfNonempty()
            g.putcells(tfndBait)
            g.putcells(tfndAddedCat)
            g.putcells(tfndInputGlider)
            g.putcells(g.parse(*gliderCat))
            for _ in range(circularHfGens[indBait]):
                g.step()
            if not ( SubpatPresent(g.parse(*gliderCat)) and \
                        SubpatPresent(g.transform(g.parse(*addedCats),0,0,*tfn))):
                worked[(indCat, indTfn, indBait)] = False
                continue
        
            # check #2: rewind glider. Do catalysts remain intact, and hf still appear?
            ClearIfNonempty()
            g.putcells(tfndBait)
            g.putcells(tfndAddedCat)
            rewindByGens = 40
            rewindedGliderPreTransf = g.transform(g.parse(*inputGlider),-(rewindByGens//4),-(rewindByGens//4) )
            g.putcells(rewindedGliderPreTransf, 0,0,*tfn)
            g.putcells(g.parse(*gliderCat))
            for _ in range(circularHfGens[indBait]+rewindByGens):
                g.step()
            if not ( SubpatPresent(g.parse(*hf_circle)) and \
                            SubpatPresent(g.parse(*gliderCat))):
                worked[(indCat, indTfn, indBait)] = False
                continue
            # check #3: is glider emitted, and when it is,
            # are additional catalysts + glider-releasing ones still intact?
            # g.warn("made it to check number 3")
            for _ in range(timesEmitted[indCat]):
                g.step()
            
            # g.warn('')
            if not (SubpatPresent(g.parse(*gliderCat)) and \
                        SubpatPresent(g.parse(*glidersEmitted[indCat])) and \
                        SubpatPresent(g.transform(g.parse(*addedCats),0,0, *tfn))):
                worked[(indCat, indTfn, indBait)] = False
                continue
            worked[((indCat, indTfn, indBait))] = True
            showWorked = False
            if showWorked:
                ClearIfNonempty()
                g.putcells(rewindedGliderPreTransf, 0,0,*tfn)
                g.putcells(tfndAddedCat)
                g.putcells(tfndBait)
                g.putcells(g.parse(*gliderCat))
                g.warn('')
g.new('chart.rle')
ClearIfNonempty()
colSep = 30
rowSep = 30
for indBait, bait in enumerate(hf_producing_baits):
    xCol = indBait * colSep
    g.putcells(g.parse(*bait), xCol,0)
    g.putcells(g.parse(*hf_producing_gliders[indBait]), xCol,0)
    g.putcells(g.parse(*hf_producing_catalysts[indBait]), xCol,0)
    for indCat, gliderCat in enumerate(gliderCats):
        for indTfn, tfn in enumerate(tfns):
            yRow = rowSep * (indTfn+1) + len(tfns)*colSep*indCat
            if worked[(indCat, indTfn, indBait)]:
                tfndBait = g.transform(g.parse(*bait),0,0,*tfn)
                tfndAddedCat = g.transform(g.parse(*hf_producing_catalysts[indBait]),0,0,*tfn)
                rewindByGens = 20
                rewindedGliderPreTfn = g.transform(g.parse(*hf_producing_gliders[indBait]),\
                                                        -(rewindByGens//4),-(rewindByGens//4))
                rewindedGlider = g.transform(rewindedGliderPreTfn, 0,0,*tfn)
                g.putcells(tfndBait, xCol, yRow)
                g.putcells(tfndAddedCat, xCol, yRow)
                g.putcells(rewindedGlider, xCol, yRow)
                g.putcells(g.parse(*gliderCat), xCol, yRow)
# stores the list of feasible arrangements to a .txt file
for indCat, gliderCat in enumerate(gliderCats):
    catTitle = "fishhook" if indCat == 0 else "chucklebait"
    with open(f'reflector_baits_{catTitle}.txt', 'w') as f:
        for indBait, bait in enumerate(hf_producing_baits):
            for indTfn, tfn in enumerate(tfns):
                if worked[(indCat, indTfn, indBait)]:
                    tnfdBaitClist = g.transform(g.parse(*bait), 0,0,*tfn)
                    corner = MinCoords(tnfdBaitClist)
                    rewindByGens = 20
                    rewindedGliderPreTfn = g.transform(g.parse(*hf_producing_gliders[indBait]),\
                                                            -(rewindByGens//4),-(rewindByGens//4))
                    rewindedGlider = g.transform(rewindedGliderPreTfn, 0,0,*tfn)
                    tfndAddedCat = g.transform(g.parse(*hf_producing_catalysts[indBait]), 0,0,*tfn)
                    combined = g.join(g.join(rewindedGlider, tnfdBaitClist),tfndAddedCat)
                    f.write(f"{corner[0]},{corner[1]},{RLE(tnfdBaitClist)},{RLE(combined)}\n")