from facette import Facette
from coords import Coords

class Cube:
    POS_TO_FACES = {
        (-1,-1,-1): "BDL",
        (-1,-1, 0): "BL",
        (-1,-1, 1): "BLU",
        (-1, 0,-1): "BD",
        (-1, 0, 0): "B",
        (-1, 0, 1): "BU",
        (-1, 1,-1): "BDR",
        (-1, 1, 0): "BR",
        (-1, 1, 1): "BRU",
        ( 0,-1,-1): "DL",
        ( 0,-1, 0): "L",
        ( 0,-1, 1): "LU",
        ( 0, 0,-1): "D",
        ( 0, 0, 1): "U",
        ( 0, 1,-1): "DR",
        ( 0, 1, 0): "R",
        ( 0, 1, 1): "RU",
        ( 1,-1,-1): "DFL",
        ( 1,-1, 0): "FL",
        ( 1,-1, 1): "FLU",
        ( 1, 0,-1): "DF",
        ( 1, 0, 0): "F",
        ( 1, 0, 1): "FU",
        ( 1, 1,-1): "DFR",
        ( 1, 1, 0): "FR",
        ( 1, 1, 1): "FRU"
    }

    staticmethod
    def format_colors(colors):
        return ",".join(sorted(list(colors)))

    staticmethod
    def faces_to_pos(faces):
        '''
        faces: orientations définissant un cube.
          ex: "ULF", cube de coin en haut, à gauche et sur l'avant
          ex: "UL", cube de côté, en haut et sur la gauche
        renvoie une position (x,y,z)
        '''
        looking_for = [0, 0, 0]
        asso = {"U":(2,1), "D":(2,-1), "F":(0,1), "B":(0,-1), "L":(1,-1), "R":(1,1)}
        for letter in faces:
            assert letter in asso
            axe, value = asso[letter]
            assert looking_for[axe] != -value
            looking_for[axe] = value
        return tuple(looking_for)

    def __init__(self, faces, parent, canvas):
        '''
        Constructeur
        faces: orientations définissant un cube.
          ex: "ULF", cube de coin en haut, à gauche et sur l'avant
          ex: "UL", cube de côté, en haut et sur la gauche
        parent: TkRubik parent
        '''
        self.__pos = Coords(*Cube.faces_to_pos(faces))
        self.__parent = parent
        facettes = {}
        self.__facettes = {}
        for f in faces:
            assert f not in self.__facettes
            color = self.__parent.COLORS[f]
            facette = Facette(color, f, self, parent.CELL_SIZE, canvas)
            facettes[f] = facette
            self.__facettes[color] = facette

        self.__facettes_list = tuple(self.__facettes.values())
        colors_list = [f.color for f in self.__facettes_list]
        self.__colors_list = tuple(colors_list)
        self.__colors = Cube.format_colors(colors_list)
        # une rotation peut occasionner des erreurs il est important
        # de calculer par rapport à une position de référence
        # on ne pourra pas changer d'axe de rotation tant qu'on sera encore en mouvement
        self.__current_rotate_deg = 0
        self.__current_rotate_vector = None

        self.__need_refresh = True

    @property
    def faces(self):
        '''
        Renvoie les faces actuelles, dans l'ordre
        ex: FLU
        '''
        #return "".join(sorted([f.face for f in self.__facettes_list]))
        # mieux
        return self.POS_TO_FACES[(self.x, self.y, self.z)]

    @property
    def colors(self):
        '''
        Renvoie l'ensemble des couleurs du cube, dans l'ordre
        '''
        return self.__colors

    @property
    def need_refresh(self):
        '''
        renvoie True s'il est nécessaire de redesciner les facettes du cube
        '''
        return self.__need_refresh

    @property
    def x(self):
        '''
        Accesseur: position x du cube
        '''
        return self.__pos.x

    @property
    def y(self):
        '''
        Accesseur: position y du cube
        '''
        return self.__pos.y

    @property
    def z(self):
        '''
        Accesseur: position z du cube
        '''
        return self.__pos.z

    def assign_rotate_vector(self, n, sens_positif):
        '''
        Avant d'amorcer un mouvement, on choisit un vecteur de rotation, normé
        n: tuple du vecteur
        sens_positif: True pour le sens direct (trigo)
        Le cube doit avoir fini un éventuellement mouvement pour changer de vecteur
        '''
        assert self.__current_rotate_deg == 0, "Changement de vecteur interdit si facette pas alignée"
        v = Coords(*n)
        assert v.norm2 == 1
        if not sens_positif:
            v = v*(-1)
        self.__current_rotate_vector = v

    def rotate(self, deg_angle):
        '''
        deg_angle: angle en degrés
        tourne le cube selon le vecteur assigné, l'axe passant par (0,0,0)
        d'un angle deg_angle,
        réactualise la position des facettes.
        Quand l'angle atteint un certain nombre de quarts de tour, tiens compte du réalignement.
        '''
        assert self.__current_rotate_vector != None, "Aucun vecteur de rotation assigné"
        self.__need_refresh = True
        self.__current_rotate_deg += deg_angle
        for facette in self.__facettes_list:
            facette.rotate(self.__current_rotate_vector, self.__current_rotate_deg)
        if abs(self.__current_rotate_deg) % 90 != 0:
            return
        quarters = (self.__current_rotate_deg // 90) % 4
        self.__current_rotate_deg = 0
        for facette in self.__facettes_list:
            facette.align()
        if quarters <= 0:
            return
        for i in range(quarters):
            self.__pos = self.__pos.rotate(self.__current_rotate_vector, 90)

    def update(self):
        '''
        mise à jour des facettes du cube dans tkInter
        '''
        self.__need_refresh = True
        for facette in self.__facettes_list:
            facette.update()

    def position_ok(self):
        '''
        renvoie True si le cube est à sa bonne place mais pas forcément bien orienté
        les centres des faces font référence
        '''
        centers_colors = self.__parent.get_centers_colors(self.x, self.y, self.z)
        good_colors = Cube.format_colors(centers_colors.values())
        return self.colors == good_colors

    def orientation_ok(self):
        '''
        renvoie True si le cube à toutes ses facettes bien orientées
        si c'est le cas, il est aussi bien placé
        '''
        centers_colors = self.__parent.get_centers_colors(self.x, self.y, self.z)
        for f in self.__facettes_list:
            if f.color != centers_colors[f.face]:
                return False
        return True

    def should_be(self):
        '''
        renvoie la position que devrait occuper ce cube
        sous forme des faces. ex: "FLU", toujours dans l'ordre
        '''
        centers_colors = self.__parent.get_centers_colors(0, 0, 0, True)
        faces = [centers_colors[f.color] for f in self.__facettes_list]
        return "".join(sorted(faces))

    def should_be_xyz(self):
        '''
        renvoie la position que devrait occuper ce cube
        sous forme de coordonnées x, y, z
        '''
        faces = self.should_be()
        return Cube.faces_to_pos(faces)

    def facette_should_be(self, face):
        '''
        face: une des face parmi "UDFBLR"
        précondition: le cube possède une facette de ce côté
        sélectionne la facette et renvoie l'orientation que devrait avoir cette facette
        les centres des faces font référence
        '''
        faces = self.faces
        assert len(face) == 1 and face in faces
        facette = [f for f in self.__facettes_list if f.face == face].pop()
        centers = self.__parent.get_centers_colors(0,0,0,True)
        return centers[facette.color]
