#!/usr/bin/python
# -*-coding:utf-8 -*

import tkinter
import random
import threading
import time


random.seed()

class HanoiGUI(tkinter.Tk):
   """Fenêtre graphique pour l'affichage des tours d'Hanoï"""

   def __init__(self, parent = None, largeur = 800, hauteur = 400, marge = 30, hauteur_socle = 10, epaisseur_socle = 6, title = "Tours d'Hanoï"):
      """Constructeur"""
      tkinter.Tk.__init__(self, parent)
      self.parent = parent

      self.largeur = largeur
      self.hauteur = hauteur
      self.marge = marge
      self.hauteur_socle = hauteur_socle
      self.epaisseur_socle = epaisseur_socle
      
      self.title(title)
      self.canvas = tkinter.Canvas(self, width = 800, height = 400, bg="white")
      self.canvas.pack()

      self.tour1 = []
      self.tour2 = []
      self.tour3 = []
      self.affichage_en_cours = False
               

               
   def __repr__(self):
      """Fonction utilisée pour l'affichage sous forme de chaîne de caractères (lorsqu'on tape directement le nom de l'objet)"""
      s = "Tours d'Hanoï\n\tTour 1 : {0}\n\tTour 2 : {1}\n\tTour 3 : {2}".format(self.tour1, self.tour2, self.tour3)
      return s
 
 

   def __str__(self):
      """Fonction utilisée pour l'affichage sous forme de chaîne de caractères (via la fonction print)"""
      s = "Tours d'Hanoï\n\tTour 1 : {0}\n\tTour 2 : {1}\n\tTour 3 : {2}".format(self.tour1, self.tour2, self.tour3)
      return s     


      
   def afficher(self, tour1, tour2, tour3):
      """Fonction d'affichage d'une configuration"""

      self._mettre_a_jour_canvas_pour_affichage(tour1, tour2, tour3)
      self.mainloop()
 
 
      
   def afficher_resolution(self, fonction_de_resolution, nombre_de_disques, frequence_de_rafraichissement = 0.02):
      """Fonction d'affichage de la méthode de résolution"""

      self._creer_tours(nombre_de_disques)
      self.affichage_en_cours = True

      #self.afficher(self.tour1, self.tour2, self.tour3)
      
      self.GUI_Thread = _GUI_Thread(self, frequence_de_rafraichissement)
      self.GUI_Thread.start()

      # self.after(10, self._mettre_a_jour_affichage)

      self.Resolve_Thread = _Resolve_Thread(self, fonction_de_resolution, nombre_de_disques)
      self.Resolve_Thread.start()
      
      self.mainloop()
   
      
      


   # FONCTIONS PRIVEES  
   
   def _creer_tours(self, nombre_de_disques):
      """Fonction d'initialisation des tours"""
      
      self.tour1 = list(range(nombre_de_disques, 0, -1))
      self.tour2 = []
      self.tour3 = []
 
 
      
   def _mettre_a_jour_canvas_pour_affichage(self, tour1, tour2, tour3):
      """Création du canvas pour l'affichage des tours (fonction privée)"""

      # On réinitialise le canvas
      self.canvas.delete("all")


      # On calcule le nombre de disques à afficher
      nombre_de_disques = self._trouver_nombre_de_disques(tour1, tour2, tour3)

      
      # On calcule les coordonnées     
      largeur_socle = (self.largeur - self.marge*4) // 3
      
      hauteur_dispo = self.hauteur - (self.hauteur_socle + self.marge*2)
      hauteur_disque = hauteur_dispo // nombre_de_disques
      
      y_milieu_socle = self.hauteur-self.marge-self.hauteur_socle
      
      x_milieu_socle1 = self.marge + largeur_socle // 2
      x_milieu_socle2 = self.largeur // 2
      x_milieu_socle3 = self.largeur - (self.marge + largeur_socle // 2)
      
      
      # On trace les socles
      self.canvas.create_rectangle(x_milieu_socle1 - largeur_socle // 2, y_milieu_socle + self.hauteur_socle, x_milieu_socle1 + largeur_socle // 2, y_milieu_socle, fill="black")
      self.canvas.create_rectangle(x_milieu_socle2 - largeur_socle // 2, y_milieu_socle + self.hauteur_socle, x_milieu_socle2 + largeur_socle // 2, y_milieu_socle, fill="black")
      self.canvas.create_rectangle(x_milieu_socle3 - largeur_socle // 2, y_milieu_socle + self.hauteur_socle, x_milieu_socle3 + largeur_socle // 2, y_milieu_socle, fill="black")
      
      self.canvas.create_rectangle(x_milieu_socle1 - self.epaisseur_socle // 2, y_milieu_socle, x_milieu_socle1 + self.epaisseur_socle // 2, self.marge, fill="black")
      self.canvas.create_rectangle(x_milieu_socle2 - self.epaisseur_socle // 2, y_milieu_socle, x_milieu_socle2 + self.epaisseur_socle // 2, self.marge, fill="black")
      self.canvas.create_rectangle(x_milieu_socle3 - self.epaisseur_socle // 2, y_milieu_socle, x_milieu_socle3 + self.epaisseur_socle // 2, self.marge, fill="black")
      
      
      # On trace les disques
      self._afficher_disques_sur_une_tour(x_milieu_socle1, y_milieu_socle, hauteur_disque, nombre_de_disques, largeur_socle, tour1)
      self._afficher_disques_sur_une_tour(x_milieu_socle2, y_milieu_socle, hauteur_disque, nombre_de_disques, largeur_socle, tour2)
      self._afficher_disques_sur_une_tour(x_milieu_socle3, y_milieu_socle, hauteur_disque, nombre_de_disques, largeur_socle, tour3)
      
      self.canvas.update()
            
                 
      
   def _trouver_nombre_de_disques(self, tour1, tour2, tour3):
      """Fonction (privée) de calcul du nombre de disques sur les tours"""
      
      nb_disques = 0
      if(tour1 != []):
         nb_disques = max(nb_disques, max(tour1))
      if(tour2 != []):
         nb_disques = max(nb_disques, max(tour2))
      if(tour3 != []):
         nb_disques = max(nb_disques, max(tour3))
      return nb_disques

      
      
   def _afficher_disques_sur_une_tour(self, x_milieu_socle, y_milieu_socle, hauteur_disque, nombre_de_disques, largeur_socle, tour):
      """Fonction "privée" pour l'affichage des disques d'une tour"""
      disques_a_afficher = list(tour)
      disques_a_afficher.reverse()
      y_courant = y_milieu_socle
      
      while(disques_a_afficher != []):
         disque = disques_a_afficher.pop()
         largeur_disque = int( largeur_socle * (((disque - 1) / (nombre_de_disques - 1)) * 0.7 + 0.2) ) 
         self.canvas.create_rectangle(x_milieu_socle - largeur_disque // 2, y_courant, x_milieu_socle + largeur_disque // 2, y_courant - hauteur_disque, fill="red")
         y_courant -= hauteur_disque


         
class _GUI_Thread(threading.Thread):
   """Thread dédié à la mise à jour de l'affichage""" 
   
   def __init__(self, hanoi, frequence_de_rafraichissement):
      threading.Thread.__init__(self)
      self.hanoi = hanoi
      self.frequence_de_rafraichissement = frequence_de_rafraichissement
      
   def run(self):     
      while self.hanoi.affichage_en_cours:
         self.hanoi._mettre_a_jour_canvas_pour_affichage(self.hanoi.tour1, self.hanoi.tour2, self.hanoi.tour3)
         time.sleep(self.frequence_de_rafraichissement)

         
         
class _Resolve_Thread(threading.Thread):
   """Thread dédié à la résolution du problème"""
   
   def __init__(self, hanoi, fonction_de_resolution, nombre_de_disques):
      threading.Thread.__init__(self)
      self.hanoi = hanoi
      self.nombre_de_disques = nombre_de_disques
      self.fonction_de_resolution = fonction_de_resolution
      
   def run(self):
      print(self.hanoi.tour1, self.hanoi.tour2, self.hanoi.tour3) 
      self.fonction_de_resolution(self.nombre_de_disques, self.hanoi.tour1, self.hanoi.tour2, self.hanoi.tour3)
      print(self.hanoi.tour1, self.hanoi.tour2, self.hanoi.tour3) 
      self.hanoi.affichage_en_cours = False