"""
Module de définition de l'IHM pour décoder un fichier
façon césar, et le runner associé (threading)
"""
# les imports nécessaires
from tkinter import Tk, Frame, Button, Entry, RIGHT, Text, END
from tkinter.filedialog import askopenfilename
from os.path import isfile
from string import ascii_letters
from time import sleep
from threading import Thread


class Decoder(Thread):
    """
    définition de la classe pour décder un fichier
    """

    # paramètre commun à toutes les classes
    travailler: bool = True

    def __init__(self, chemin: str, callback: callable) -> None:
        """
        constructeur de notre runner
        :param chemin:  type str, l'emplacement du fichier à décoder
        :param callback: type callable, la fonction à appeler
        pour message texte à transmettre
        """
        # appel du constructeur de la classe mère
        Thread.__init__(self)
        # sauvegarde par propriétés des informations
        self.chemin = chemin
        self.callback = callback

    def stop(self) -> None:
        """
        méthode pour forcer l'arrêt du traitement
        :return: None
        """
        # simple, on met le flag à faux !
        self.travailler = False

    def run(self) -> None:
        """
        méthode centrale: le traitement à réaliser
        :return: None
        """
        # temporisation, permet de tester le bouton "stop"
        # sinon, traitrement trop rapide :-)
        sleep(10)
        # est ce que le bouton stop a été pressé ?
        if self.travailler:
            # non... alors, faisons le traitement
            # quelques mesures de contrôles
            # TODO: vérifier que c'est un fichier txt
            # avec la lib mimetype et la fonction guess_type
            if self.chemin and isfile(self.chemin):
                # le fichier existe bien, tout est ok
                # ouvrons le en lecture
                # ne pas oublier de forcer l'encodage-> pas de surprise !
                with open(self.chemin, "r", encoding="utf-8") as fichier:
                    # on charge le contenu en mémoire
                    contenu = fichier.read()
                # on sort du bloc pour libérer le fichier (close)
                # on récupère le contenu, et envoi à l'IHM
                self.callback(self.__decoder_contenu(contenu))

    def __decoder_contenu(self, contenu: str) -> str:
        """
        méthode interne pour décoder le contenu
        :param contenu: type str, le texte à décoder
        :return: type str, le texte décodé
        """
        # stockage interne du décodage
        contenu_decode = []
        # raccourci vers les méthodes, et valeurs calculées
        index_ascii = ascii_letters.index
        taille_ascii = len(ascii_letters)
        # pour chaque caractère du texte
        for caractere in contenu:
            # est ce une lettre à traiter ?
            if caractere in ascii_letters:
                # oui, quelle position ?
                position = index_ascii(caractere)
                # faisons le décalage
                nouvelle_position = (position - 13) % taille_ascii
                # et trouvons la nouvelle lettre à stocker
                contenu_decode.append(ascii_letters[nouvelle_position])
            else:
                # on ne touche à rien; stockage direct du caractère
                contenu_decode.append(caractere)
        # il reste à générer une chaine depuis la liste !
        contenu_decode_texte = "".join(contenu_decode)
        return contenu_decode_texte


class IhmCesar:
    """
    classe de définition de l'IHM
    """

    # les paramètres pour le fonctionnement de la classe
    # l'emplacement du fihcer à traiter
    chemin: str = ""
    # pointeur vers le runner ?
    traitement = None

    def __init__(self) -> None:
        """
        le constructeur, création de l'IHM !
        """
        # notre fenêtre principale
        fenetre_principale = Tk()
        # une frame pour le choix de l'emplacement
        zone_haut = Frame(fenetre_principale)
        zone_haut.pack()
        # la zone de saisie (option, saisir l'emplacement)
        self.saisie = Entry(zone_haut)
        self.saisie.grid(row=0, column=0)
        # un bouton pour fenêtre de dialogue: choisir le fichier
        Button(zone_haut, text="Fichier ?", command=self.__choisir_fichier).grid(
            row=0, column=1
        )
        # seconde frame: les boutons d'action
        zone_milieu = Frame(fenetre_principale)
        # deux boutons, l'un pour lancer le traitement
        # on pointe la méthode decoder
        Button(zone_milieu, text="Décoder", command=self.__decoder).pack(side=RIGHT)
        # l'autre pour l'arrêter
        # on pointe la méthode stop
        Button(zone_milieu, text="Stop", command=self.__stop).pack(side=RIGHT)
        zone_milieu.pack()
        # le bas: la zone d'affichage du résultat
        self.texte = Text(fenetre_principale)
        self.texte.pack()
        # essentiel:lancer l'interface graphique !
        fenetre_principale.mainloop()

    def ecrire_message(self, message: str) -> None:
        """
        méthode callback pour écrire des informations
        dans la zone de texte
        :param message: type str, le message à afficher
        :return: None
        """
        # simple appel à la zone de texte !
        # insertion du texte
        self.texte.insert(0.0, message)

    def __decoder(self) -> None:
        """
        méthode pour lancement le traitement
        par threading
        :return:None
        """
        # création du runner via la classe
        # on passe la méthode callback en entrée
        # on stocke l'objet dans une propriété -> accès à tout moment!
        self.traitement = Decoder(self.chemin, self.ecrire_message)
        # lancement du runner, ie, du traitement
        self.traitement.start()

    def __stop(self) -> None:
        """
        méthode pour arrêter le traitement
        :return: None
        """
        # si un traitement est en cours
        # nous avons un runner et non None
        if self.traitement:
            # on demande au runner de s'arrêter !
            self.traitement.stop()

    def __choisir_fichier(self):
        """
        méthode pour choisir l'emplacement
        :return: None
        """
        # utilisation de la fenêtre de dialogue
        self.chemin = askopenfilename()
        # on nettoye la zone de saisie
        self.saisie.delete(0, END)
        # on insère le chemin choisi
        self.saisie.insert(0, self.chemin)
