# -*- coding: utf-8 -*-
"""
Created on Tue Aug 11 18:10:00 2015

@author: dconduche
"""

import matplotlib.pyplot as plt
import numpy as np


"""Remarques d'ordre général :
 * Tout mot du programme a une nature (instruction, nom de variable ou de fonction, etc),
 et tout paramètre passé à une fonction (par exemple passé à open(...)) a un type :
 int, string, list, etc...
 * Respecter précisément la syntaxe demandée (DL vs Dl etc...) sur les points
 techniques est très important : quand vous travaillerez en équipe, si votre
 collègue fait un appel à la fonction fusee() et que vous l'avez appelée Fusee(),
 le programme va juste planter...
 * Il n'y a pas « une » solution : souvent vous avez plusieurs façon de coder la fonction
"""




"""~~~~~~ EXERCICE 1 ~~~~~~"""

alpha = ""
for i in range(97, 123):
    alpha += chr(i)

""" On pouvait aussi taper « alpha = "abcdefghijklmnopqrstuvwxyz" » """


def cesar(n, texte):
    ntexte = ''
    for car in texte:
        if car in alpha:
            nombre = (alpha.index(car) + n) % 26
            ntexte += alpha[nombre]
        else:
            ntexte += car
    return ntexte.upper()

#texte_code = cesar(3, "oralensam")
#print("texte codé:", texte_code)
#texte_clair = cesar(-3, texte_code.lower()).lower()
#print("texte clair:", texte_clair)

""" Pour commenter/décommenter un bloc de texte dans spyder : Ctrl-1 """





"""~~~~~~ EXERCICE 2 ~~~~~~"""

"""Question 1:
Il existe 26 décallages possibles donc 26 chiffrements différents"""

texte = "WODRYNOLBEDOPYBMOYXDOCDODYEDOCVOCYCCSLSVSDOC"
#### On teste :
#for i in range(26):
#    print(i, cesar(i, texte[:24]).lower(), '\n')

### Puis on transforme en fonction :


def brute_force(texte_code):
    for i in range(26):
        print(i, cesar(i, texte_code[:24]).lower(), '\n')
        # Les 24 premiers caractères suffisent amplement pour vérifier
    return None
#brute_force(texte.lower())

"""Donc i = 16"""
#print(cesar(16, texte.lower()))


"""Question 2:
La fonction ouvre le fichier dont le nom est contenu dans la variable
« nom_fichier » (au format str, avec le chemin d'accès) en lecture
(« 'r' » comme read) et retourne le contenu sous forme d'une chaîne de caractères"""


def lecture(nom_fichier):
    with open(nom_fichier, 'r') as fichier_python:
        resultat = fichier_python.read()
    return resultat

#print(lecture("FichiersCryptes/texte1.txt"))
## Le nom contient aussi le chemin d'accès

"""Question 3: force brute
On teste toute les possibilités -- certe i = 0 peut être écarté après q2"""

#texte_code = lecture("FichiersCryptes/texte1.txt").lower()
#
#print(cesar(18, texte_code).lower())  #Dans mon cas, c'est i=18





"""~~~~~~ EXERCICE 3 ~~~~~~"""

"""Question 1:"""
def creer_permut(n):
    L = list(range(n))
    permut = []
    while L != []:  # alternative : « for i in range(n): »
        j = np.random.randint(len(L))
        permut.append(L.pop(j))
    return permut

#print(creer_permut(26))

"""Question 2:"""

"""Il y en a 26! = 403291461126605635584000000
Obtenu avec « np.math.factorial(26)) »"""





"""~~~~~~ EXERCICE 4 ~~~~~~"""

"""Question 1:"""
permut = creer_permut(26)

"""Question 2:"""
def crypter_permut(per, texte):
    n_texte = ""
    for caractere in texte:
        if caractere in alpha:
            n_texte += alpha[per[alpha.index(caractere)]]
        else:
            n_texte += caractere
    return n_texte

#texte_clair = "un petit texte pour tester le programme de chiffrement"
#texte_code = crypter_permut(permut, texte_clair)
#print(texte_code)

"""Question 3:
Il suffit d'inverser la permutation :
 * soit recoder une fonction decrypter_permut,
 * soit coder une fonction qui inverse la permutation (ce qui est fait ici)"""

def inverser(per):
    inv_per = [per.index(i) for i in range(len(per))]
    return inv_per

### Suite du code précédent (q2)
#inv_permut = inverser(permut)
#print(crypter_permut(inv_permut, texte_code))

"""Question 4:
Essayons une méthode identique à celle pour casser César. Admettons
 qu'il y ait 7 milliards d'humains sur terre,
 qu'il faille 1 seconde pour vérifier si la permutation est la bonne.
Alors il faut 26!/(7*19**9) secondes, i.e. 26!/(7*10**9*60*60*24*365) années.
Ce qui fait... beaucoup trop! La fonction n → n! est une fonction qui croît *très*
rapidement.

Donc non, on ne peut pas casser le code par cette méthode."""





"""~~~~~~ EXERCICE 5 ~~~~~~"""

"""Question 1:"""
def freq(texte):
    L = [texte.count(lettre) for lettre in alpha]
    n = sum(L)
    return [nbr/n for nbr in L]

texte = lecture("FichiersCryptes/texte1.txt").lower()
freq_code = freq(texte)
#print(freq_code)

"""Question 2-3:"""
def histo_freq(y, y_0):
    x = np.array(range(26))
    w = .3
    plt.bar(x, y, w, 0)
    plt.bar(x+w, y_0, w, 0, color='red')
    plt.xticks(x, list(alpha))  # Légende sur l'axe des x
    plt.show()
    return None

ffr_prct = [9.42, 1.02, 2.64, 3.39, 15.87, 0.95, 1.04, 0.77, 8.41, 0.89, 0.00,
            5.34, 3.24, 7.15, 5.14, 2.86, 1.06, 6.46, 7.90, 7.26, 6.24, 2.15,
            0.00, 0.30, 0.24, 0.32]
freq_fr = [x/100 for x in ffr_prct]

#histo_freq(freq_code, freq_fr)

"""Ici un seul candidat semble se détacher : le « m », donc il faut décaler
(codage de César) de -8 (ou de 18) pour décoder, ce qui est bien le cas."""

texte = lecture("FichiersCryptes/texte2.txt").lower()
freq_code = freq(texte)
#histo_freq(freq_code, freq_fr)

"""Deux candidats se détachent : le « m » et le « q ».
Il faut tester les lettres doubles pour distinguer. """


"""Question 4 (bonus):"""

def freq_doubles(texte):
    L = [texte.count(lettre+lettre) for lettre in alpha]
    resultat = []
    for i in range(len(alpha)):
        numero_lettre = L.index(max(L))  # Numéro de la lettre la plus fréquente
        resultat.append(alpha[numero_lettre])  # on rajoute en bout de liste
        L[numero_lettre] = -1  # et on passe à -1 pour trouver le max suivant
    return resultat

#print(freq_doubles(texte))

"""Décidément le résultat n'est pas clair, on peut essayer d'étudier les
bigrammes les plus fréquents. Il serait aussi utile de retourner les fréquences
et pas uniquement l'ordre."""

def freq_bigrammes(texte):
    n = len(alpha)  # Certes, =26, mais ça ne coûte rien et c'est plus robuste
    L = np.zeros((n, n))  # Attention : parenthèses du couple (n, n)
    for i in range(n):
        for j in range(n):
            L[i, j] = texte.count(alpha[i]+alpha[j])
    return L
f_big = freq_bigrammes(texte)
big = np.array([[lettre1+lettre2 for lettre2 in alpha] for lettre1 in alpha])

#print("Bigrammes présents plus de 100 fois :")
#print(big[f_big > 100])
#print(f_big[f_big > 100])

"""Visiblement, c'est plutôt le « q » qui correspond au « e ».
cf. http://www.apprendre-en-ligne.net/crypto/stat/francais.html
Les trigrammes et un recoupement entre bigrammes et lettres doubles permettrait
de trouver les lettres les plus fréquentes. Une fois celles-ci trouvées, il ne
reste plus beaucoups de lettres à trouver."""


"""Question 5:"""






"""~~~~~~ EXERCICE 6 ~~~~~~"""

"""Question 1:"""
def vigenere(cle, texte):
    N = len(cle)  # longueur de la clé
    ntexte = ''
    for i in range(len(texte)):
        car = texte[i]
        if texte[i] in alpha:
            ntexte += alpha[(alpha.index(car) +
                             alpha.index(cle[i % N])) % 26].upper()
        else:
            ntexte += car
    return ntexte

#texte_clair = "unexempledechiffrement"
#cle = "python"
#texte_code = vigenere(cle, texte_clair)
#print(texte_code)

"""Question 2: même méthode, en décalant dans l'autre sens."""

def de_vigenere(cle, texte):
    N = len(cle)  # longueur de la clé
    ntexte = ''
    for i in range(len(texte)):
        car = texte[i]
        if texte[i] in alpha:
            ntexte += alpha[(alpha.index(car) -  #  Juste un + qui devient -
                             alpha.index(cle[i % N])) % 26]
        else:
            ntexte += car
    return ntexte

#print(de_vigenere(cle, texte_code.lower()))




"""~~~~~~ EXERCICE 7 ~~~~~~"""

"""Question 1:"""

"""Pour chaque lettre de la clé il y a 26 possibilités, donc au total
26**N possibilités de clés. Dès que N est suffisament grand (>20 par exemple),
les méthodes de force brute les plus naïves ne fonctionnent plus."""


"""Question 2:"""

texte_code = lecture("FichiersCryptes/texte3.txt")

def decoupe_texte(nom_fichier, N):
    texte = lecture(nom_fichier)
    L = []
    for i in range(N):
        L.append(texte[i::N].lower())
    return L

#print(decoupe_texte(texte, 5))



def lettre_freq_max(texte):
    L = []
    for lettre in alpha:
        L.append(texte.count(lettre))
    return alpha[L.index(max(L))]



#L_code = decoupe_texte('FichiersCryptes/texte3.txt')
#L_decode = []
#cle = ''
#for texte in L_code:
#    lettre = lettre_freq_max(texte)
#    cle += lettre
#    n = (- alpha.index(lettre) + alpha.index('e'))
#    L_decode.append(cesar(n, texte))

#print(cle)

#texte = ""
#for i in range(len(L_decode[-1])):
#    for txt in L_decode:
#        texte += txt[i]
#print(texte)
"""Question 3:"""


