# -*- coding: utf-8 -*-
"""
Created on Sat May 25 15:22:41 2024

@author: Stéphane Pasquet
@url : https://www.mathweb.fr/euclide/generateur-de-labyrinthes/
"""

# pyinstaller Labyrinthe_version_complete.py
from random import randint
from tkinter import filedialog
from tkinter import *
from tkinter.messagebox import showinfo
from PIL import ImageGrab
import datetime
import os
#from copy import deepcopy
import shutil
from collections import deque

global version
version = '0.3 complète -- Mathweb.fr -- Stéphane Pasquet -- 2020-06-19'

"""
Classe Graphe
"""

class Graphe:
    def __init__(self):
        self.Liste = {}

    def addArete(self, u, v):
        if v not in self.Liste:
            self.Liste[v] = []

        if u not in self.Liste:
            self.Liste[u] = []

        self.Liste[u].append(v)
        self.Liste[v].append(u)

    def chemins(self,start,end):
        pile = deque()
        pile.append((start, [start]))
        # ajout pour avoir le chemin le plus court, on pondère chaque chemin par le nombre de sommets qu'il contient
        result = []

        while pile:
            (S, path) = pile.pop()
            list_nodes = [n for n in self.Liste[S] if n not in path]
            for i in list_nodes:
                if i == end:
                    e = path + [i]
                    result.append([len(e) , e])
                    chemins = True
                else:
                    pile.append((i, path + [i]))

        if chemins:
            result.sort(key=lambda col:col[0])
            return result[0][1]
        else:
            return False

"""
Construction de l'objet Labyrinthe
"""

def solution():
    L = Graphe()
    # MursV[i] contient la liste des murs verticaux de la ligne i
    # MursH[i] contient la liste des murs horizontaux du dessus de la ligne i
    for i in range( globNbrLignes ):
        for j in range( globNbrColonnes ):
            cell = 'C'+str(i)+'-'+str(j) # nom de la cellule courante
            cellR = 'C'+str(i)+'-'+str(j+1) # nom de la cellule de droite
            cellB = 'C'+str(i+1)+'-'+str(j) # nom de la cellule du bas
            if MursH[i+1][j] == False:
                L.addArete(cell, cellB)
            if MursV[i][j+1] == False:
                L.addArete(cell,cellR)

    cellstart = 'C'+str(Entree[0])+'-'+str(Entree[1])
    cellend = 'C'+str(Sortie[0])+'-'+str(Sortie[1])

    way = L.chemins(cellend,cellstart) # liste des cellules à relier
    draw(way)

"""
Conversion en LaTeX
"""
def preambule(*packages):
    p = ""
    for i in packages:
        p = p+"\\usepackage{"+i+"}\n"
    return p

def convert_to_latex(*args):
    dirname = filedialog.askdirectory(initialdir = "/",title = "Sélectionner le dossier de destination")
    dirname += '/Labyrinthe/'
    document = "\\documentclass{standalone}\n"
    document += preambule('tikz') + "\n\\begin{document}\n\\begin{tikzpicture}\n"
    dim = 1 # dimension d'une cellule
    for ligne in range(globNbrLignes*2+1):
        if ligne % 2 == 0:
            for colonne in range(globNbrColonnes*2+1):
                if colonne % 2 != 0:
                    if MursH[ligne//2][(colonne-1)//2] == True:
                        document += "\\draw[very thick] ("+str((colonne-1)*dim/2)+","+str(ligne*dim/2)+") -- ("+str((colonne+1)*dim/2)+","+str(ligne*dim/2)+");\n"
        else:
            for colonne in range(globNbrColonnes*2+1):
                if (colonne%2) != 1:
                    if MursV[(ligne-1)//2][colonne//2] == True:
                        document += "\\draw[very thick] ("+str(colonne*dim/2)+","+str((ligne-1)*dim/2)+") -- ("+str(colonne*dim/2)+","+str((ligne+1)*dim/2)+");\n"
    if args:
        delta = dim/2
        liste_cellules = []
        for i in args[0][0]:
            l = i[1:].split('-') # l = coordonnées
            liste_cellules.append(l)

        for i in range( len(liste_cellules)-1 ):
            document += "\\draw[very thick,red] (" + str(delta + int(liste_cellules[i][1])*dim) + "," + str(delta + int(liste_cellules[i][0])*dim) + ") -- (" + str(delta + int(liste_cellules[i+1][1])*dim) + "," + str(delta + int(liste_cellules[i+1][0])*dim) + ");\n"

    document += "\\end{tikzpicture}\n\\end{document}"
    # sauvegarde du fichier TEX dans le répertoire courant
    if args:
        filenameTEX = "labyrinthe"+suffixePicture()+"_solution.tex"
    else:
        filenameTEX = "labyrinthe"+suffixePicture()+".tex"
    fichier = open(filenameTEX,"x")
    fichier.write(document)
    fichier.close()

    # compilation PdfLaTeX dans le répertoire courant
    cmd = 'echo Compilation du fichier '+filenameTEX
    cmd = "pdflatex  --shell-escape -synctex=1 -interaction=nonstopmode "+filenameTEX
    os.system(cmd)

    # création des répertoires
    os.makedirs(dirname+'latex/', exist_ok = True)
    os.makedirs(dirname+'pdf/', exist_ok = True)

    # copie des fichiers TEX et PDF
    filenamePDF = filenameTEX[:-3]+'pdf'
    cmd = 'echo Copie de '+filenamePDF+' vers '+dirname+'pdf/'+filenamePDF
    os.system(cmd)
    shutil.copy(filenamePDF, dirname+'pdf/'+filenamePDF)
    cmd = 'echo Copie de '+filenameTEX+' vers '+dirname+'latex/'+filenameTEX
    os.system(cmd)
    shutil.copy(filenameTEX, dirname+'latex/'+filenameTEX)
    cmd = 'echo Suppression de '+filenamePDF[:-3] + '*'
    os.system(cmd)
    cmd = 'del '+filenamePDF[:-3] + '*'
    os.system(cmd) 


"""
Dessin du labyrinthe
"""

def suffixePicture():
    date = datetime.datetime.now()
    year = date.year
    month = date.month
    day = date.day
    hour = date.hour
    minute = date.minute
    seconde = date.second
    return str(year)+str(month)+str(day)+str(hour)+str(minute)+str(seconde)

def draw(*args):
    Lab = Tk()
    if args:
        Lab.title('Solution')
    else:
        Lab.title('Labyrinthe')
    Lab.iconbitmap("labyrinthe.ico")
    canvas = Canvas(Lab,width = (globNbrColonnes + 2) * 20 , height = (globNbrLignes + 2) * 20 , bg = "white")
    #canvas.create_text((globNbrColonnes + 2) * 10,(globNbrLignes + 2) * 10,text="Mathweb.fr",fill="#dedede",angle=45,font=('Helvetica', '30'))
    canvas.pack()

    def convert_latex():
        if args:
            convert_to_latex(args)
        else:
            convert_to_latex()

    def save_canvas():
        x, y = canvas.winfo_rootx(), canvas.winfo_rooty()
        w, h = canvas.winfo_width(), canvas.winfo_height()
        if args:
            filenamePNG = 'labyrinthe'+suffixePicture()+'_solution.png'
        else:
            filenamePNG = 'labyrinthe'+suffixePicture()+'.png'
        ImageGrab.grab((x,y,x+w,y+h)).save(filenamePNG)
        dirname = filedialog.askdirectory(initialdir = "/",title = "Sélectionner le dossier de destination")
        dirname += '/Labyrinthe/images/'
        os.makedirs(dirname, exist_ok=True)
        shutil.copy(filenamePNG, dirname+filenamePNG)
        avertiss = Tk()
        avertissK = Canvas(avertiss,width=500,height=100)
        avertissK.pack()
        avertiss.title('Labyrinthe sauvegardé')
        avertiss.iconbitmap("labyrinthe.ico")
        avertissK.create_text(250,40,text='Labyrinthe sauvegardé sous:',font=("Helvetica", 10),fill='red')
        avertissK.create_text(250,60,text=dirname+filenamePNG,font=("Helvetica", 10),fill='red')
        os.remove(filenamePNG)

    delta = 20
    dim = 20 # dimensions d'une cellule
    for ligne in range(globNbrLignes*2+1):
        if ligne % 2 == 0:
            for colonne in range(globNbrColonnes*2+1):
                if colonne % 2 != 0:
                    if MursH[ligne//2][(colonne-1)//2] == True:
                        # mur vertical d'une cellule
                        canvas.create_line(delta + (colonne-1)*dim/2 , delta + ligne*dim/2 , delta + (colonne+1)*dim/2 , delta + ligne*dim/2,width=2)
        else:
            for colonne in range(globNbrColonnes*2+1):
                if (colonne%2) != 1:
                    if MursV[(ligne-1)//2][colonne//2] == True:
                        canvas.create_line(delta + colonne*dim/2 , delta + (ligne-1)*dim/2 , delta + colonne*dim/2 , delta + (ligne+1)*dim/2,width=2)

    if args: # si c'est la solution
        delta += dim/2
        liste_cellules = []
        for i in args[0]:
            l = i[1:].split('-') # l = coordonnées
            liste_cellules.append(l)

        for i in range( len(liste_cellules)-1 ):
            canvas.create_line(delta + int(liste_cellules[i][1])*dim , delta + int(liste_cellules[i][0])*dim , delta + int(liste_cellules[i+1][1])*dim , delta + int(liste_cellules[i+1][0])*dim , width=2 , fill='red')

    # TODO : mettre les boutons au même niveau
    boutons = Frame(Lab)
    boutons.pack()
    Button(boutons,text='PNG',command=save_canvas).grid(row=0,column=0)
    Button(boutons,text='LaTeX + PDF',command=convert_latex).grid(row=0,column=2,padx=5)
    if len(args) == 0:
        Button(boutons,text='Solution',command=solution).grid(row=0,column=1,padx=5)
    Label(Lab,text='\n').pack()
    mainloop()

def ChoisirEntreeEtSortie():
        """Définition de la cellule d'entrée et de sortie du labyrinthe"""
        global Entree
        global Sortie
        global MursV
        global MursH

        a = randint(0,1)

        if a == 0:                                              # On enleve aléatoirement alors un mur, ici le mur est vertical, puis aléatoirement si c'est un mur de gauche ou de droite
                b = randint(0,1)
                d = globNbrLignes-1
                c = randint(0,d)

                if b == 0:                                      # On enlève un mur à gauche et on choisit au hasard lequel
                        MursV[c][0] = False
                        MursV[(d-c)][globNbrColonnes] = False
                        Entree = [c,0]
                        Sortie = [d-c,globNbrColonnes-1]
                else:                                           # Autrement c'est un mur de droite et on choisit au hasard lequel
                        MursV[c][globNbrColonnes] = False
                        MursV[d-c][0] = False
                        Entree = [c,globNbrColonnes-1]
                        Sortie = [d-c,0]
        else:                                                   # Autrement on enlève un mur horizontal
                b = randint(0,1)
                d = globNbrColonnes-1
                c = randint(0,d)

                if b == 0:                                      # On enlève un mur en haut
                        MursH[0][c] = False
                        MursH[globNbrLignes][(d-c)] = False
                        Entree = [0,c]
                        Sortie = [globNbrLignes-1,(d-c)]
                else:                                           # Sinon on enlève un mur du bas du labyrinthe
                        MursH[globNbrLignes][c] = False
                        MursH[0][(d-c)] = False
                        Entree = [globNbrLignes-1,c]
                        Sortie = [0,(d-c)]

"""Destruction d'un mur entre 2 cellules en fonction des cordonnées des cellules A(xA,yA) et B(xB,yB) """

def OuverturedunMurEntre2Cellules(xA, yA, xB, yB):

        if xA == xB: # Les deux cellules étant sur la meme ligne, on enlève le mur vertical entre elles
                if yA < yB:
                        MursV[xA][yB] = False
                else:
                        MursV[xA][yA] = False
        if yA == yB: # Les deux cellules sont sur la meme colonne, on enlève le mur horizontal entre elles
                if xA < xB:
                        MursH[xA+1][yA] = False
                else:
                        MursH[xB+1][yA] = False

"""Définition de l'algorithme permettant la création du labyrinthe"""

def AlgoExplorationExhaustive():
        CellulesEnregistrees = []
        labyrinthe = [ [False] * globNbrColonnes for i in range(globNbrLignes) ]
        a = Entree[0]
        b = Entree[1]
        CaseSuivante = 0
        labyrinthe[a][b] = True
        CellulesEnregistrees.append([a,b])

        while True:
                # On décrit les différentes possibilités d'ouverture entre les cellules,
                # Différentes cas sont possibles en fonction de la position des cellules.

                listeVoisinsVisites = []

                if a == 0:
                        if b != 0 and b != (globNbrColonnes-1):         # Côté supérieur du labyrinthe excluant les coins
                                if labyrinthe[a+1][b] == False:
                                        listeVoisinsVisites.append([a+1,b])
                                if labyrinthe[a][b-1] == False:
                                        listeVoisinsVisites.append([a,b-1])
                                if labyrinthe[a][b+1] == False:
                                        listeVoisinsVisites.append([a,b+1])
                        else:
                                if b == 0:                              # Coin supérieur gauche
                                        if labyrinthe[a+1][b] == False:
                                                listeVoisinsVisites.append([a+1,b])
                                        if labyrinthe[a][b+1] == False:
                                                listeVoisinsVisites.append([a,b+1])
                                else:                                   # Coin supérieur droit
                                        if labyrinthe[a+1][b] == False:
                                                listeVoisinsVisites.append([a+1,b])
                                        if labyrinthe[a][b-1] == False:
                                                listeVoisinsVisites.append([a,b-1])
                else:
                        if b == 0:
                                if a == (globNbrLignes - 1):            # Coin inférieur gauche
                                        if labyrinthe[a][b+1] == False:
                                                listeVoisinsVisites.append([a,b+1])
                                        if labyrinthe[a-1][b] == False:
                                                listeVoisinsVisites.append([a-1,b])
                                else:                                   # Côté gauche du labyrinthe en excluant les coins
                                        if labyrinthe[a][b+1] == False:
                                                        listeVoisinsVisites.append([a,b+1])
                                        if labyrinthe[a-1][b] == False:
                                                        listeVoisinsVisites.append([a-1,b])
                                        if labyrinthe[a+1][b] == False:
                                                        listeVoisinsVisites.append([a+1,b])
                        else:
                                if a == (globNbrLignes - 1):
                                        if b == (globNbrColonnes - 1):  # Coin inférieur droit
                                                if labyrinthe[a-1][b] == False:
                                                        listeVoisinsVisites.append([a-1,b])
                                                if labyrinthe[a][b-1] == False:
                                                        listeVoisinsVisites.append([a,b-1])
                                        else:                           # Côté inférieur du labyrinthe en excluant les coins
                                                if labyrinthe[a][b-1] == False:
                                                        listeVoisinsVisites.append([a,b-1])
                                                if labyrinthe[a][b+1] == False:
                                                        listeVoisinsVisites.append([a,b+1])
                                                if labyrinthe[a-1][b] == False:
                                                        listeVoisinsVisites.append([a-1,b])
                                else:
                                        if b == (globNbrColonnes - 1):  # Côté droit du labyrinthe en excluant les coins
                                                if labyrinthe[a+1][b] == False:
                                                        listeVoisinsVisites.append([a+1,b])
                                                if labyrinthe[a][b-1] == False:
                                                        listeVoisinsVisites.append([a,b-1])
                                                if labyrinthe[a-1][b] == False:
                                                        listeVoisinsVisites.append([a-1,b])
                                        else:                           # N'est pas une case en bordure du labyrinthe
                                                if labyrinthe[a+1][b] == False:
                                                        listeVoisinsVisites.append([a+1,b])
                                                if labyrinthe[a][b-1] == False:
                                                        listeVoisinsVisites.append([a,b-1])
                                                if labyrinthe[a][b+1] == False:
                                                        listeVoisinsVisites.append([a,b+1])
                                                if labyrinthe[a-1][b] == False:
                                                        listeVoisinsVisites.append([a-1,b])

        # On vient de créer une liste des voisins que l'on peut visiter

                if (len(listeVoisinsVisites) > 0):
                        nbrVoisinsNonVisites = 0
                        if (len(listeVoisinsVisites) > 1):
                                nbrVoisinsNonVisites = randint(0,(len(listeVoisinsVisites)-1))
                        CaseSuivante = listeVoisinsVisites.pop(nbrVoisinsNonVisites) # On choisit un voisin au hasard
                        CellulesEnregistrees.append([a,b]) # On sauvegarde la cellule courante
                        OuverturedunMurEntre2Cellules(a,b,CaseSuivante[0],CaseSuivante[1]) # On ouvre le mur entre les deux cellules
                else:
                        if (len(CellulesEnregistrees) > 0):
                                CaseSuivante = CellulesEnregistrees.pop()
                        else:
                                break

                a = CaseSuivante [0]
                b = CaseSuivante [1]
                labyrinthe[a][b] = True

""" Pour le menu """

def post_action():
    global globNbrLignes
    global globNbrColonnes
    global MursV
    global MursH
    global labyrinthe

    labyrinthe = 0
    CellulesEnregistrees = 0
    globNbrColonnes = 0
    globNbrLignes = 0
    Entree = 0
    Sortie = 0
    MursH = 0
    MursV = 0
    numLab = 0

    globNbrColonnes = int(colonnes.get())

    if globNbrColonnes == 0:
        if niveau == 1:
            m = 5
            M = 15
        elif niveau == 2:
            m = 20
            M = 30
        else:
            m = 60
            M = 70
        globNbrColonnes = randint(m,M)
        
    globNbrLignes = int(lignes.get())
    if globNbrLignes == 0:
        if niveau != 3:
            globNbrLignes = randint(m,M)
        else:
            globNbrLignes = randint(m//2,M//2)

    MursH = [ [True] * globNbrColonnes for i in range(globNbrLignes+1) ]
    MursV = [ [True] * ( globNbrColonnes + 1 ) for i in range(globNbrLignes) ]

    ChoisirEntreeEtSortie()

    AlgoExplorationExhaustive()

    draw()

def makeentry(parent, caption, width=None, **options):
    Label(parent, text=caption).pack()
    entry = Entry(parent, **options)
    if width:
        entry.config(width=width)
    entry.pack()
    return entry

def menu():
    global lignes
    global colonnes

    def recup_niveau():
        global niveau
        niveau = niveauTk.get()

    menu = Tk()
    menu.title('Générateur de labyrinthe - '+version)
    menu.iconbitmap("labyrinthe.ico")
    menuCanvas = Canvas(menu,width = 350 , height = 50)
    menuCanvas.pack()
    
    niveauTk = IntVar()
    numLigne = IntVar()
    numCol = IntVar()

    Label(menu,text="Générateur de labyrinthe",font=("Helvetica", 20)).pack()

    lignes = makeentry(menu, "Nombre de lignes :", 2 , textvariable = numLigne)
    colonnes = makeentry(menu, "\nNombre de colonnes :", 2 , textvariable = numCol)
    Label(menu,text='\n-- OU --\n').pack()
    Radiobutton(menu,text='Génération aléatoire : petite taille',variable=niveauTk,value=1,command=recup_niveau).pack()
    Radiobutton(menu,text='Génération aléatoire : taille moyenne',variable=niveauTk,value=2,command=recup_niveau).pack()
    Radiobutton(menu,text='Génération aléatoire : grande taille',variable=niveauTk,value=3,command=recup_niveau).pack()
    Label(menu,text='\n').pack()
    Button(text='Valider',command=post_action).pack()
    Label(menu,text='\n').pack()
    Button(text='Conditions d\'utilisation',command=conditions).pack()
    Label(menu,text='\n').pack()

    menu.mainloop()
    
def conditions():
    txt = "Ce logiciel est la propriété de Stéphane Pasquet.\n"
    txt += "ATTENTION_______________________________________\n"
    txt += "La version actuelle du logiciel ne supporte pas les débordements : si vous entrez un nombre de lignes ou colonnes et que le labyrinthe ne tient pas sur votre écran, il n'est pas possible de voir les boutons pour exporter l'image.\n"
    txt += "La version complète devrait supprimer ce défaut.\n"
    txt += "------------------------------------------------\n"
    txt += "Le logiciel est téléchargeable sur https://www.mathweb.fr/euclide/generateur-de-labyrinthes/.\n\n"
    txt += "Vous ne pouvez pas utiliser ce programme à des fins commerciales sans mon autorisation."
    
    showinfo(title="Conditions d'utilisation", message=txt)


if __name__ == '__main__':

    menu()
