import sympy as sym
from sympy import poly

a = sym.Symbol('a')
b = sym.Symbol('b')
c = sym.Symbol('c')
d = sym.Symbol('d')
e = sym.Symbol('e')

def make_T(Fpair): #Subfunction for the group builder
    def T(L):
        return [Fpair[0][0](Fpair[1][0](L)[0])[0], (Fpair[1][0](L)[1] + Fpair[0][0](Fpair[1][0](L)[0])[1])]
    return T

#Takes in a specific set of maps and genereates the group G_k using those maps as a base.
def Build_Group(S, base): 
    G = []
    out = set()
    i = 0
    for x in S:
        out.add( tuple(x[1][0]) )
        G.append(x)
    while i < len(G):
        for f in G:
            for g in G:
                T = make_T([g, f])
                res = T(base)
                res2 = tuple(res[0])
                if not(res2 in out):
                    out.add(res2)
                    G.append([T, res])
                T = make_T([f, g])
                res = T(base)
                res2 = tuple(res[0])
                if not(res2 in out):
                    out.add(res2)
                    G.append([T, res])
            if len(G) >= 120:
                break
        if len(G) >= 120:
            break
    
    return G

def genXk(k): #Generates the set G_k will act upon
    X_k = set()
    for m in range(1,k):
        for n in range(1,k):
            for l in range(1,k):
                for p in [t for t in range(0,k) if t not in [m,n,l]]:
                    for q in [t for t in range(0,k) if t not in [m,n,l, (m+n+l - p) %k]]:
                        X_k.add((m,n,l,p,q))
    return X_k

#Takes in G_k and an even integer k >= 2 and determines the orbits of the action of G_k on X_k
def genorbits(G, k):
    X_k = genXk(k)
    Y = genXk(k)
    orbits = set()
    while len(Y) > 0:
        for x in X_k:
            if not(x in Y):
                continue
            orbitx = set()
            for f in G:
                y = tuple([l % k for l in f[0](list(x))[0]])
                if f[0](list(x))[1]%2 == 0:
                    sign = 1
                else:
                    sign = -1
                y2 = (y, sign)
                if not(y in Y):
                    for z in orbitx:
                        if z[0] == y:
                            v = list(z)
                            v.append(sign)
                            orbitx.discard(z)
                            orbitx.add(tuple(v))
                            break
                elif(not(y2 in orbitx)):
                    orbitx.add(y2)
                    Y.discard(y)
            orbitx.add(x)
            orbits.add(tuple(orbitx))
            Y.discard(x)
    return orbits

#Handler for large scale orbit generation
def orbit_lists(G, n, m):
    for k in range(n, m+1, 2):
        print(f"Generating orbit files for k = {k}...")
        O = genorbits(G, k)
        with open("Raw_Orbits_k=" + str(k) + ".txt", mode = "w") as file:
            if len(O) == 1:
                file.write(f"The 1 orbit of the action of G_k on X_k for k = {k} is,\n\n")
            else:
                file.write(f"The {len(O)} orbits of the action of G_k on X_k for k = {k} are,\n\n")
            
            for orb in O:
                for x in orb:
                    if type(x[0]) is int:
                        orbrep = x
                        break                 
                file.write(f"The orbit generated by {orbrep} (who has native sign {(-1)**(orbrep[2] + orbrep[4])}) is listed below and is of size {len(orb) - 1}\n")
                for x in orb:
                    if x != orbrep:
                        signset = set()
                        for i in range(1, len(x)):
                            signset.add(x[i])
                        if len(signset) == 1:
                            file.write(str(x[0]) + " with native sign " + str((-1)**(x[0][2] + x[0][4])) + " and transformation sign " + str(signset) + "\n")
                        else:
                            file.write(str(x[0]) + " with native sign " + str((-1)**(x[0][2] + x[0][4])) + " and transformation signs " + str(signset) + "\n")
                file.write("\n")

        with open("Orbits_k=" + str(k) + ".txt", mode = "w") as file:
            if len(O) == 1:
                file.write(f"The 1 orbit of the action of G_k on X_k for k = {k} is,\n\n")
            else:
                file.write(f"The {len(O)} orbits of the action of G_k on X_k for k = {k} are,\n\n")
            
            for orb in O:
                contribution = 0
                for x in orb:
                    if type(x[0]) is int:
                        orbrep = x
                        repsign = (-1)**(orbrep[2]+orbrep[4])
                        break                 
                file.write(f"The orbit generated by {orbrep} is listed below and is of size {len(orb) - 1}\n")
                for x in orb:
                    if x != orbrep:
                        signset = set()
                        for i in range(1, len(x)):
                            signset.add(x[i])
                        if len(signset) == 1:
                            sign = (-1)**(x[0][2] + x[0][4])*repsign*list(signset)[0]
                            contribution += sign
                            if sign == 1:
                                file.write("+" + str(x[0]) + "\n")
                            else:
                                file.write("-" + str(x[0]) + "\n")
                        else:
                            file.write("+/-" + str(x[0]) + "\n")
                file.write(f"The contribution of this orbit is {contribution}\n")
                file.write("\n")
    return 1

#main
def main():
    def T_1(L):
        NL = [L[1] - L[3], L[0] - L[3], L[2] - L[3], -L[3], L[4] - L[3]]
        sign = 0
        return [NL, sign]

    def T_2(L):
        NL = [L[0], L[0] - L[3], L[0] - L[4], L[0] - L[1], L[0] - L[2]]
        sign = L[0] + L[1] + L[2] + L[3] + L[4]
        return [NL, sign]

    def T_3(L):
        NL = [L[1] - L[3], L[1], L[1] - L[4], L[1] - L[0], L[1] - L[2]]
        sign = L[0] + L[1] + L[2] + L[3] + L[4]
        return [NL, sign]

    def T_4(L):
        NL = [L[0], L[1], L[4] - L[2], L[0] + L[1] - L[3], L[4]]
        sign = L[0] + L[4]
        return [NL, sign]

    def T_5(L):
        NL = [L[0], L[3] - L[1], L[2], L[3],  L[0] + L[2] - L[4]]
        sign = L[0] + L[3]
        return [NL, sign]

    def T_6(L): 
        NL = [L[3] - L[0], L[1], L[2], L[3], L[1] + L[2] - L[4]]
        sign = L[1]
        return [NL, sign]

    def T_7(L):
        NL = [L[3] - L[0], L[3] - L[1], L[2], L[3], L[3] + L[4] - L[0] - L[1]]
        sign = L[0] + L[1]
        return [NL, sign]

    def T_8(L):
        NL = [L[0], L[2], L[1], L[4], L[3]]
        sign = 0
        return [NL, sign]

    base = [a, b, c, d, e]

    S = [[T_1, T_1(base)], [T_2, T_2(base)], [T_3, T_3(base)], [T_4, T_4(base)], [T_5, T_5(base)], [T_6, T_6(base)], [T_7, T_7(base)], [T_8, T_8(base)]]
    print("Performing initial set-up...")
    G = Build_Group(S, base)
    print("This program will generate orbit files for the action of G_k on X_k for all even integers k >=2 in a user given range.\nThese files will be saved as 'orbit_k=i.txt' as they are generated.\nWarning: This code may take exceedingly long to run on some machines for large values of k")
    n = int(input("Enter a starting integer n (this number must be >= 2 and even): "))
    m = int(input("Enter a finishing integer m (only one file will be generated if m = n, integer must be even): "))
    orbit_lists(G, n, m)
    
main()
