from sympy.combinatorics.permutations import Permutation
from sympy.utilities.iterables import variations, rotate_left
from sympy.core.symbol import symbols
from sympy.matrices import Matrix

def symmetric(n):
    """
    Generates the symmetric group of order n, Sn.

    Examples
    ========

    >>> from sympy.combinatorics.permutations import Permutation
    >>> Permutation.print_cyclic = True
    >>> from sympy.combinatorics.generators import symmetric
    >>> list(symmetric(3))
    [Permutation(2), Permutation(1, 2), Permutation(2)(0, 1),
     Permutation(0, 1, 2), Permutation(0, 2, 1), Permutation(0, 2)]
    """
    for perm in variations(list(range(n)), n):
        yield Permutation(perm)

def cyclic(n):
    """
    Generates the cyclic group of order n, Cn.

    Examples
    ========

    >>> from sympy.combinatorics.permutations import Permutation
    >>> Permutation.print_cyclic = True
    >>> from sympy.combinatorics.generators import cyclic
    >>> list(cyclic(5))
    [Permutation(4), Permutation(0, 1, 2, 3, 4), Permutation(0, 2, 4, 1, 3),
     Permutation(0, 3, 1, 4, 2), Permutation(0, 4, 3, 2, 1)]

    See Also
    ========
    dihedral
    """
    gen = list(range(n))
    for i in range(n):
        yield Permutation(gen)
        gen = rotate_left(gen, 1)

def alternating(n):
    """
    Generates the alternating group of order n, An.

    Examples
    ========

    >>> from sympy.combinatorics.permutations import Permutation
    >>> Permutation.print_cyclic = True
    >>> from sympy.combinatorics.generators import alternating
    >>> list(alternating(3))
    [Permutation(2), Permutation(0, 1, 2), Permutation(0, 2, 1)]
    """
    for perm in variations(list(range(n)), n):
        p = Permutation(perm)
        if p.is_even:
            yield p

def dihedral(n):
    """
    Generates the dihedral group of order 2n, Dn.

    The result is given as a subgroup of Sn, except for the special cases n=1
    (the group S2) and n=2 (the Klein 4-group) where that's not possible
    and embeddings in S2 and S4 respectively are given.

    Examples
    ========

    >>> from sympy.combinatorics.permutations import Permutation
    >>> Permutation.print_cyclic = True
    >>> from sympy.combinatorics.generators import dihedral
    >>> list(dihedral(3))
    [Permutation(2), Permutation(0, 2), Permutation(0, 1, 2), Permutation(1, 2),
     Permutation(0, 2, 1), Permutation(2)(0, 1)]

    See Also
    ========
    cyclic
    """
    if n == 1:
        yield Permutation([0, 1])
        yield Permutation([1, 0])
    elif n == 2:
        yield Permutation([0, 1, 2, 3])
        yield Permutation([1, 0, 3, 2])
        yield Permutation([2, 3, 0, 1])
        yield Permutation([3, 2, 1, 0])
    else:
        gen = list(range(n))
        for i in range(n):
            yield Permutation(gen)
            yield Permutation(gen[::-1])
            gen = rotate_left(gen, 1)


def rubik_cube_generators():
    """Return the permutations of the 3x3 Rubik's cube, see
    http://www.gap-system.org/Doc/Examples/rubik.html
    """
    a = [[(1,3,8,6),(2,5,7,4),(9,33,25,17),(10,34,26,18),(11,35,27,19)],
      [(9,11,16,14),(10,13,15,12),(1,17,41,40),(4,20,44,37),(6,22,46,35)],
      [(17,19,24,22),(18,21,23,20),(6,25,43,16),(7,28,42,13),(8,30,41,11)],
      [(25,27,32,30),(26,29,31,28),(3,38,43,19),(5,36,45,21),(8,33,48,24)],
      [(33,35,40,38),(34,37,39,36),(3,9,46,32),(2,12,47,29),(1,14,48,27)],
      [(41,43,48,46),(42,45,47,44),(14,22,30,38),(15,23,31,39),(16,24,32,40)]]
    return [Permutation([[i - 1 for i in xi] for xi in x], size=48) for x in a]


def rubik(n):
    """Return permutations for an nxn Rubik's cube.

    Permutations returned are for rotation of each of the slice
    from the face up to the last face for each of the 3 sides (in this order):
    front, right and bottom. Hence, the first n - 1 permutations are for the
    slices from the front.
    """

    if n < 2:
        raise ValueError('dimension of cube must be > 1')

    # 1-based reference to rows and columns in Matrix
    def getr(f, i):
        return faces[f].col(n - i)
    def getl(f, i):
        return faces[f].col(i - 1)
    def getu(f, i):
        return faces[f].row(i - 1)
    def getd(f, i):
        return faces[f].row(n - i)
    def setr(f, i, s):
        faces[f][:,n - i] = Matrix(n, 1, s)
    def setl(f, i, s):
        faces[f][:,i - 1] = Matrix(n, 1, s)
    def setu(f, i, s):
        faces[f][i - 1,:] = Matrix(1, n, s)
    def setd(f, i, s):
        faces[f][n - i,:] = Matrix(1, n, s)

    # motion of a single face
    def cw(F, r=1):
        for _ in range(r):
            face = faces[F]
            rv = []
            for c in range(n):
                for r in range(n - 1, -1, -1):
                    rv.append(face[r, c])
            faces[F] = Matrix(n, n, rv)
    def ccw(F):
        cw(F, 3)

    # motion of plane i from the F side;
    # fcw(0) moves the F face, fcw(1) moves the plane
    # just behind the front face, etc...
    def fcw(i, r=1):
        for _ in range(r):
            if i == 0:
                cw(F)
            i += 1
            temp = getr(L, i)
            setr(L, i, list((getu(D, i))))
            setu(D, i, list(reversed(getl(R, i))))
            setl(R, i, list((getd(U, i))))
            setd(U, i, list(reversed(temp)))
            i -= 1
    def fccw(i):
        fcw(i, 3)

    # motion of the entire cube from the F side
    def FCW(r=1):
        for _ in range(r):
            cw(F)
            ccw(B)
            cw(U)
            t = faces[U]
            cw(L)
            faces[U] = faces[L]
            cw(D)
            faces[L] = faces[D]
            cw(R)
            faces[D] = faces[R]
            faces[R] = t
    def FCCW():
        FCW(3)

    # motion of the entire cube from the U side
    def UCW(r=1):
        for _ in range(r):
            cw(U)
            ccw(D)
            t = faces[F]
            faces[F] = faces[R]
            faces[R] = faces[B]
            faces[B] = faces[L]
            faces[L] = t
    def UCCW():
        UCW(3)

    # defining the permutations for the cube

    U, F, R, B, L, D = names = symbols('U, F, R, B, L, D')

    # the faces are represented by nxn matrices
    faces = {}
    count = 0
    for fi in range(6):
        f = []
        for a in range(n**2):
            f.append(count)
            count += 1
        faces[names[fi]] = Matrix(n, n, f)

    # this will either return the value of the current permutation
    # (show != 1) or else append the permutation to the group, g
    def perm(show=0):
        # add perm to the list of perms
        p = []
        for f in names:
            p.extend(faces[f])
        if show:
            return p
        g.append(Permutation(p))

    g = [] # container for the group's permutations
    I = list(range(6*n**2)) # the identity permutation used for checking

    # define permutations corresonding to cw rotations of the planes
    # up TO the last plane from that direction; by not including the
    # last plane, the orientation of the cube is maintained.

    # F slices
    for i in range(n-1):
        fcw(i)
        perm()
        fccw(i) # restore
    assert perm(1) == I

    # R slices
    # bring R to front
    UCW()
    for i in range(n-1):
        fcw(i)
        # put it back in place
        UCCW()
        # record
        perm()
        # restore
        # bring face to fron
        UCW()
        fccw(i)
    # restore
    UCCW()
    assert perm(1) == I

    # D slices
    # bring up bottom
    FCW()
    UCCW()
    FCCW()
    for i in range(n-1):
        # turn strip
        fcw(i)
        # put bottom back on the bottom
        FCW()
        UCW()
        FCCW()
        # record
        perm()
        # restore
        # bring up bottom
        FCW()
        UCCW()
        FCCW()
        # turn strip
        fccw(i)
    # put bottom back on the bottom
    FCW()
    UCW()
    FCCW()
    assert perm(1) == I

    return g
