import matplotlib.pyplot as plt
import random

def caractereVersEntier (car : str )-> int :
    """ 1.1
        caractereVersEntier (car : str) -> int
        entree : car chaine reduite a un caractère que l'on veut tranformer en entier
        sortie : la position de car dans l'alphabet (A:1, B:2, ..., Z:26) - 0 si car est un espace
    """
    if car == " " :
        return 0
    else :
        return ord(car) - 64   # 65 correspond au code ASCII de 'A' - 66 : 'B' - .... - 'Z':90
        # ou
        # return ord(car) - ( ord('A') -1)

def entierVersCaractere (position : int) -> str:
    """ 1.2
        caractereVersEntier (position : int) -> str
        entree : position la position de car dans l'alphabet
        sortie : car chaine associe à position " " si position à 0
    """
    if position == 0 :
        return " "
    else :
        return chr(position + 64)
    # Autre écriture possible
    # return " " if i == 0 else chr(position + 64)

def encode(texte: str)-> list:
    """ 1.3
        encode(texte : str) -> list
        entree : texte chaine de caractère que l'on doit transformer en liste d'entiers
        sortie : liste d'entiers qui correspond à l'endodage de texte
    """
    liste=[]
    for car in texte :
        liste.append( caractereVersEntier(car) ) #transformation de chaque caractere de texte en entier que l'on ajoute à liste
    return liste
    # Autre approche : en utilisant des listes par compréhension
    # return [ caractereVersEntier(car) for car in texte ]

def decode( liste : list ) -> str:
    """ 1.4
        decode(liste : list) -> str
        entree : liste, liste d'entiers  que l'on doit transformer en chaine
        sortie : ch, chaine de caracteres correspondant au decodage des entiers de la liste
    """
    ch =""
    for nb in liste :
        ch  += entierVersCaractere (nb) #concaténation de la chaine ch avec l'entier nb converti en caractère
    return ch

from random import shuffle
def genereCode( ) -> list :
    """ 1.5
        genereCode ( ) -> list
        sortie : code, liste d'entiers distribués aléatoirement
    """
    alphabet = list(range(1, 27))  # Generation d'une liste contenant les entiers de 1 à 27
    shuffle(alphabet)  # Mélange l'alphabet (opération en place : agit directement sur alphabet)
    code = [0] + alphabet  # On respecte f(0)=0
    return code

def chiffrer(texte : str, cle : list) -> str:
    """ 1.6
        chiffrer(texte : str, cle: list) -> str
        entrees : texte, chaine de caractères à chiffrer
                : cle, liste d'entiers qui correspond à la cle de chiffrement
        sortie  : texte_chiffre chaine qui correspond au texte chiffré avec cle
    """
    # Etape 1 : encodage de texte sous forme de liste d'entiers
    liste_indices_texte_encode = encode(texte)
    print( "Etape 1 : texte encode", liste_indices_texte_encode )

    # Etape 2 : generation de la liste d'entiers liste_indices_texte_chiffre
    # qui correspond a la correspondance de chaque entier avec la cle
    liste_indices_texte_chiffre = []  # Pour stocker les indices du texte chiffre
    for nb in liste_indices_texte_encode:
        nb_chiffre = cle[nb]  # Chiffre le caractère
        liste_indices_texte_chiffre.append( nb_chiffre )  # On ajoute a la liste des indices chiffrée
    print( "Etape 2 : liste_indices_texte_chiffre ", liste_indices_texte_chiffre )

    # Etape 3 : generation de la chaine de caractères correspondante => Chaine chiffrée
    texte_chiffre = decode( liste_indices_texte_chiffre ) # on transforme la liste de nb en chaine
    print( "Etape 3 : texte_chiffre",texte_chiffre )
    return texte_chiffre

def dechiffrer(texte : str, cle: list) -> str:
    """ 1.7
        dechiffrer(texte : str, cle: list) -> str
        entree  : texte chaine de caractères à dechiffrer
                : cle liste d'entiers qui correspond à la cle de chiffrement
        sortie  : texte_dechiffre chaine qui correspond au texte dechiffré
    """
    #Etape 1 : encodage sous forme de liste d'entiers
    liste_indices_texte_chiffre = encode(texte)

    #Etape 2 : generation de la liste d'entier correspondant au dechiffrement
    liste_indices_texte_dechiffre = []
    for nb in liste_indices_texte_chiffre:
        pos = cle.index(nb)  # On cherche la position de la lettre dans le code
        liste_indices_texte_dechiffre.append(pos)  # Ajoute a la liste d'entiers dechiffrée

    #Etape 3 : generation de la chaine correspondant
    texte_dechiffre = decode(liste_indices_texte_dechiffre)
    return texte_dechiffre

# =========================================================
# Etape 2 : Analyse de frequences de caracteres
#==========================================================
import numpy as np
def frequenceCaracteres(texte: str)-> np.array:
    """ 2.1 :
        frequenceCaracteres(textecomplet: str)-> array
        entree : texte, chaine de caracteres dont on veut établir la fréquence des caractères
        sortie : tab_frequences, tableau comportant a la ieme position la fréquence
        d'apparition de la ieme lettre de l'alphabet.
    """
    tab_frequences  = np.zeros(27)  # tableau contenant les fréquences des lettres initialisé à 0
    texteEncode = encode(texte)     # Convertit le texte complet en liste de nombres
    for i in range(27):
        tab_frequences [i] += texteEncode.count(i)  # Compte le nombre d'occurence de la lettre encodée i
    tab_frequences  = tab_frequences [1:]           # Retire l'espace du tableau pour ne pas stocker sa frequence
    return tab_frequences  / sum(tab_frequences)    # Normalisation par le nombre de lettre dans le texte

def afficheFrequences( freq  : np.array):
    #===== affichage diagramme en batons des frequences ===
    alphabet =  [chr(k) for k in range(65, 65 + 26)] #Generation des lettres pour affichage
    plt.bar(alphabet, freq, color = "purple")

    # titre des abcisses
    plt.xlabel("Caractère")

    # titre des ordonnées
    plt.ylabel("Fréquences")
    plt.title(" Fréquences / caractère ")
    plt.show()

def trieFrequencesCaracteres(frequences: np.array)-> list:
    """ 2.2 :
        trieFrequencesCaracteres(frequences: array)-> list
        entree : frequences, tableau comportant les fréquences de chaque caractère
                (a la ieme position, la fréquence d'apparition de la ie lettre de l'alphabet).
        sortie : liste des indices des caractères du plus présent au moins présent
    """
    nbFrequences = len(frequences)
    indices = list(range(nbFrequences))  # liste d'indices qui sera triée en même temps que frequences. Au depart :  [0,1, ..,25]
    for i in range(nbFrequences):
        for j in range(0, nbFrequences - i - 1):
            if frequences[j] < frequences[j + 1]:  # échange si l'élement est plus grand que le suivant
                frequences[j], frequences[j + 1] = frequences[j + 1], frequences[j]
                indices[j], indices[j + 1] = indices[j + 1], indices[j]  # Même procédure sur les indices
    return indices

#indices  = trieFrequencesCaracteres(freq)
#print ( "indices frequences triees", indices )

def trouveCle(frequencesTriees_texte_reference : np.array, frequencesTriees_texte : np.array)-> list:
    """ 2.3 :
        trouveCle(frequencesTriees_texte_reference : list, frequencesTriees_texte : list)-> list
        entree : deux listes representant les fréquences triees du texte de reference et du texte a dechiffrer
        sortie : liste correspondant à la cle de chiffrage obtenue
    """
    # Calcul des fréquences
    cle = [0 for i in range(26)]
    for i in range(26):
        cle[frequencesTriees_texte_reference[i]] = frequencesTriees_texte[i] + 1  # +1 pour tenir compte de l'espace dans l'encodage
    cle = [0, ] + cle  # Ajout de l'espace
    return cle


if __name__ == '__main__':

    # Etape 1
    # tests elementaires des fonctions
    print( caractereVersEntier ('C') ) # affiche 3
    print( caractereVersEntier (' ') ) # affiche 0

    print( entierVersCaractere (3) ) # affiche C
    print( entierVersCaractere (0) ) # affiche un espace

    print( 'Encodage' , encode('BONJOUR'))  # affiche [2, 15, 14, 10, 15, 21, 18]
    print( 'Encodage' , encode('BON JOUR')) # affiche [2, 15, 14, 0, 10, 15, 21, 18]

    print( 'Decodage',decode([2, 15, 14, 10, 15, 21, 18])) #affiche BONJOUR
    print( 'Decodage',decode([2, 15, 14, 0, 10, 15, 21, 18])) #affiche BON JOUR

    print ( genereCode () ) # exemple de generation
#[0, 5, 13, 3, 2, 25, 19, 9, 20, 16, 14, 15, 8, 4, 6, 22, 18, 1, 23, 26, 10, 21, 11, 24, 17, 12, 7]

    cle = [0, 5, 13, 3, 2, 25, 19, 9, 20, 16, 14, 15, 8, 4, 6, 22, 18, 1, 23, 26, 10, 21, 11, 24, 17, 12, 7]
    print ( "chiffrer", chiffrer('BONJOUR', cle) ) # affiche MVFNVUW
    print ( dechiffrer('MVFNVUW', cle) ) # affiche BONJOUR

    # Enchainement
    print("Etape 1")
    texte_test = "INFORMATIQUE TRONC COMMUN"
    cle = genereCode ()
    texte_chiffre = chiffrer(texte_test, cle)
    texte_dechiffre = dechiffrer(texte_chiffre, cle)

    print("\ttexte clair : ", texte_test)
    print("\tClé de chiffrement : ", cle)
    print("\ttexte chiffré : ", texte_chiffre)
    print("\ttexte déchiffré : ", texte_dechiffre)

    # Etape 2
    print("\nEtape  2")

    texte = "ODQHGHVYVHZRB IZAF YUZHF FDABBH Y EDGMHOOFDF GD NDBBYLD EYRB GD VI GZNIQDV JAH GZRBVHVAYHV ARD FDUHBHZR ED RZNPFDABDB RZVHZRB FDRGZRVFDDB DR IFDNHDFD YRRDD OZRGVHZRB QHBVDB NYRHIAQYVHZR ED OHGMHDFB LFYIMHJADB  RZAB YPZFEDFZRB GDVVD YRRDD QDB RZVHZRB ED PYBDB ED EZRRDDB EHGVHZRRYHFDB IFZLFYNNYVHZR ESRYNHJAD VMDZFHD ED KDAW DV HRVDQQHLDRGD YFVHOHGHDQQD RZVYNNDRV QDB PYBDB ED Q YIIFDRVHBBYLD YAVZNYVHJAD DR PFDO AR UYBVD IFZLFYNND"

    import os.path
    # ouverture du fichier
    chemin_dossier = os.path.abspath(os.path.dirname(__file__))    # recuperation du chemin courant du script
    print(chemin_dossier)     # affiche c:\Users\eclermont\CPGE IPT\EC\ITC_S3_TD0
    chemin_complet = os.path.join(chemin_dossier, "ducote.txt") # ajout du fichier (si fichier au meme niveau que le script)
    print(chemin_complet)     # affiche c:\Users\eclermont\CPGE IPT\EC\ITC_S3_TD0\ducote.txt
    fic = open(chemin_complet)

    # ou chemin en absolu. + simple à ecrire mais attention : si on veut lancer ce script dans un autre environnement :
    #  il faut modifier le chemin d'acces au fichier
    #fic = open('C:\\Users\\eclermont\\CPGE IPT\\EC\\ITC_S3_TD0\\ducote.txt', 'r')

    texte_reference = fic.read() # recuperation du contenu du fichier
    fic.close()

    """ ou
    with open(chemin_complet) as fic:
        texte_reference = fic.read()
    """

    freq = frequenceCaracteres("BONJOUR")
    #print ( freq )
    """  0.         0.14285714 0.28571429 0.         0.         0.14285714
     0.         0.         0.14285714 0.         0.         0.
     0.         0.        ]
    """
    afficheFrequences(freq)

    stat = frequenceCaracteres(texte_reference)
    afficheFrequences(stat)

    frequence_texte = frequenceCaracteres (texte)
    frequence_texte_reference = frequenceCaracteres (texte_reference)

    frequencesTriees_texte_reference = trieFrequencesCaracteres(frequence_texte_reference)
    frequencesTriees_texte = trieFrequencesCaracteres(frequence_texte)

    code = trouveCle(frequencesTriees_texte_reference, frequencesTriees_texte)

    texte_dechiffre = dechiffrer(texte, code)

    print("\t Liste des lettres les plus fréquentes en français : ", decode(np.array(frequencesTriees_texte_reference) + 1))
    print("\t texte déchiffré avec l analyse de frequences de caracteres : ", texte_dechiffre[:49], "...")
