'''
module: tkrubik
description: définition du rubik cube entier et gestion graphique
'''

from tkinter import *
from cube import Cube

class TkRubik:
    # représente un Rubik Cube en vue isométrique
    FULL_SIZE = 300 # taille d'une facette
    CELL_SIZE = FULL_SIZE/3
    WIDTH = 300*2
    HEIGHT = 300*2
    COLORS = {'U':'red', 'D':'blue', 'L':'cyan', 'R':'yellow', 'F':'#eda31a', 'B':'green'}

    REFRESH_TIME = 100

    MOVES_FILTERS = {
        "U": lambda c: c.z == 1,
        "D": lambda c: c.z == -1,
        "F": lambda c: c.x == 1,
        "B": lambda c: c.x == -1,
        "L": lambda c: c.y == -1,
        "R": lambda c: c.y == 1,
        "U*": None,
        "F*": None,
        "R*": None,
        "U2": lambda c: c.z == 0,
        "F2": lambda c: c.x == 0,
        "R2": lambda c: c.y == 0,
        "D2": lambda c: c.z == 0,
        "B2": lambda c: c.x == 0,
        "L2": lambda c: c.y == 0
    }

    MOVES_VECTORS = {
        "U": (0,0,1),
        "D": (0,0,-1),
        "F": (1,0,0),
        "B": (-1,0,0),
        "L": (0,-1,0),
        "R": (0,1,0),
        "U*": (0,0,1),
        "F*": (1,0,0),
        "R*": (0,1,0),
        "U2": (0,0,1),
        "F2": (1,0,0),
        "R2": (0,1,0),
        "D2": (0,0,-1),
        "B2": (-1,0,0),
        "L2": (0,-1,0)
    }


    SUBCUBES_FACES = ("U", "LU", "FU", "RU", "BU", "FLU", "FRU", "BRU", "BLU", "F", "FL", "L", "BL", "B", "BR", "R", "FR", "D", "DF", "DFL", "DL", "BDL", "BD", "BDR", "DR", "DFR")

    def __init__(self):
        '''
        Constructeur
        initialise la fenêtre tkinter et crée le rubik cube
        '''
        self.__tk = Tk()
        self.__tk.title('Rubik cube')
        self.__canvas = Canvas(self.__tk, height=self.HEIGHT+100, width=self.WIDTH+100)
        self.__canvas.pack()
        # il y aura 6*9 = 54 facettes faisant autant de polygones à dessiner
        cubes = {faces:Cube(faces, self, self.__canvas) for faces in self.SUBCUBES_FACES}
        self.__cubes_list = tuple(cubes.values())
        self.__centers = tuple([cubes[f] for f in self.COLORS.keys()])

    def __get_cubes(self, filter = None):
        '''
        renvoie une liste de cubes
        filter: fonction prédicat de filtrage ayant un cube pour argument
        '''
        if filter == None:
            return self.__cubes_list
        return [c for c in self.__cubes_list if filter(c)]

    def __exec(self, move, sens, anim = True):
        '''
        move: mouvement parmi les autorisés: "U", "L"...
        sens: "+" pour sens direct ou "-"
        anim: jouer l'animation, True par défaut
        '''
        assert sens == '+' or sens == '-'
        assert move in self.MOVES_FILTERS
        filter = self.MOVES_FILTERS[move]
        cubes = self.__get_cubes(filter)
        n = self.MOVES_VECTORS[move]
        for cube in cubes:
            cube.assign_rotate_vector(n, sens == '+')
        if not anim:
            for cube in cubes:
                cube.rotate(90)
            return
        for i in range(9):
            for cube in cubes:
                cube.rotate(10)
                cube.update()
            self.__tk.update()
            self.__tk.after(self.REFRESH_TIME)

    # Fonctions publiques
    def scramble(self, melange):
        '''
        melange: suite de mouvement direct parmi U, D, L, R, F, B
        exécute ces mouvements pour mélanger le cube
        update une seule fois à la fin
        '''
        for m in melange:
            self.__exec(m, '+', False)
        self.update()

    def U(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon U dans le sens demandé
        '''
        self.__exec("U", sens)

    def U2(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon U2 (tranche horizontale du milieu) dans le sens demandé
        '''
        self.__exec("U2", sens)

    def D(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon D dans le sens demandé
        '''
        self.__exec("D", sens)

    def F(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon F dans le sens demandé
        '''
        self.__exec("F", sens)

    def F2(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon F (tranche entre F et B) dans le sens demandé
        '''
        self.__exec("F2", sens)

    def B(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon B dans le sens demandé
        '''
        self.__exec("B", sens)

    def L(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon L dans le sens demandé
        '''
        self.__exec("L", sens)

    def L2(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon L2 (entre L et R, regardée depuis L) dans le sens demandé
        '''
        self.__exec("L2", sens)

    def R(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon R dans le sens demandé
        '''
        self.__exec("R", sens)

    def R2(self, sens):
        '''
        sens: '+' ou '-'
        exécute la rotation selon R2 (entre L et R, regardée depuis R) dans le sens demandé
        '''
        self.__exec("R2", sens)

    def turn_left(self):
        '''
        exécute la rotation de l'ensemble du cube vers la gauche
        '''
        self.__exec("U*", "-")

    def turn_left(self):
        '''
        exécute la rotation de l'ensemble du cube vers la droite
        '''
        self.__exec("U*", "+")

    def turn_down(self):
        '''
        exécute la rotation de l'ensemble du cube vers le bas, vu depuis F
        '''
        self.__exec("R*", "+")

    def turn_up(self):
        '''
        exécute la rotation de l'ensemble du cube vers le haut, vu depuis F
        '''
        self.__exec("R*", "-")

    def get_cube_wich_faces_should_be(self, 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 le cube qui DEVRAIT avoir cette position (mais n'y est pas forcément)
        les centres des faces font référence
        '''
        x, y, z = Cube.faces_to_pos(faces)
        return self.get_cube_wich_xyz_should_be(x, y, z)

    def get_cube_wich_xyz_should_be(self, x, y, z):
        '''
        x, y, z: coordonnées
        renvoie le cube qui DEVRAIT avoir cette position (mais n'y est pas forcément)
        les centres des faces font référence
        '''
        centers = self.get_centers_colors(x, y, z)
        colors = Cube.format_colors(centers.values())
        for c in self.__cubes_list:
            if c.colors == colors:
                return c
        raise ValueError("Cette position est introuvable.")

    def get_cube_with_faces(self, 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 le cube ayant actuellement cette position,
        même si ce n'est pas sa bonne position
        les centres des faces font référence
        '''
        x, y, z = Cube.faces_to_pos(faces)
        return self.get_cube_by_coords(x, y, z)

    def get_cube_by_coords(self, x, y, z):
        '''
        x, y, z: coordonnées
        renvoie le cube ayant actuellement cette position,
        même si ce n'est pas sa bonne position
        les centres des faces font référence
        '''
        for c in self.__cubes_list:
            if c.x == x and c.y == y and c.z == z:
                return c
        raise ValueError("Cette position est introuvable.")

    def get_centers_colors(self, x, y, z, reverse = False):
        '''
        x, y, z: coordonnées
        reverse: sens de l'association renvoyée
        renvoie un dictionnaire donnant, pour les cubes aux centres des faces,
          l'association face - couleur
          si x, y, z = 0, 0, 0, concerne toutes les faces
          sinon seulement les faces dont les coordonnées correspondent aux non nuls parmi x, y, z
          si reverse = False, le dictionnaire est donné face -> couleur
          sinon c'est couleur -> face
        '''

        if x == 0 and y == 0 and z == 0:
            if reverse:
                return {c.colors:c.faces for c in self.__centers}
            return {c.faces:c.colors for c in self.__centers}
        if reverse:
            return {c.colors:c.faces for c in self.__centers if c.x == x and x != 0 or c.y == y and y != 0 or c.z == z and z != 0}
        return {c.faces:c.colors for c in self.__centers if c.x == x and x != 0 or c.y == y and y != 0 or c.z == z and z != 0}

    def update(self, canvas = None):
        '''
        raffraichissement du dessin du rubik cube
        '''
        for cube in self.__cubes_list:
            if cube.need_refresh:
                cube.update()
        self.__tk.update()

    def end(self):
        '''
        pour le maintien final de l'affichage
        '''
        self.__tk.mainloop()
