F = GF(2)
R.<x> = PolynomialRing(F)
S.<xi> = R.quotient_ring(x^64 + 1)
T.<X> = PolynomialRing(S)
Ti.<Xi> = T.quotient_ring(X^64 + 1)
Tj.<Xj> = T.quotient_ring(X^64)

def num_to_poly(num):
    out = 0
    i = 0
    while num != 0:
        if num % 2 == 1:
            out += xi^i
        i += 1
        num //= 2
    return out

def poly_to_num(poly):
    out = 0
    for i in range(0, 64):
        if poly[i] == 1:
            out |= 1 << i
    return out


with open("./07_weird_assembly_machine.data") as f:
    data = [int(line) for line in f.readlines() if line]

f = reduce(lambda a, b: a * X + b, [num_to_poly(a) for a in data[:128]])
g = reduce(lambda a, b: a * X + b, [num_to_poly(a) for a in data[128:]])

fg = f(g)
fgi = Ti([fg[i] for i in range(0, fg.degree())])
fgj = Tj([fg[i] for i in range(0, fg.degree())])

print("\n\n======= f(g(x)) =======")
for i in reversed(range(0, 64)):
    print(poly_to_num(fgi.lift()[i]))

print("\n\n======= f(g(x)) =======")
for i in reversed(range(0, 64)):
    print(poly_to_num(fgj.lift()[i]))