#! /usr/bin/env python3
# -*- coding: utf-8 -*-
""" TP - classification du verre """

import os
import csv
import random
import copy
from math import sqrt

nom_fichier = "Base-Donnees-verre.csv"
""" Fonctions déjà données """


# Pour récupérer la base de données :
def recuperer_base(nom_fichier, nom_base):
    """Récupère la base de donnees sauvegardee dans le fichier nom_fichier.
    Le nom du fichier doit comporter l'extension.
    Sauvegarde la base de donnee dans la variable nom_base sous forme de liste"""

    # gestion de l'erreur si le fichier n'est pas trouvé :
    assert os.path.exists(nom_fichier), "fichier non trouvé"
    # ouverture du fichier et lecture du csv :
    fichier = open(nom_fichier)
    lignes = csv.reader(fichier, delimiter=",")
    num_ligne = 0
    for ligne in lignes:
        if num_ligne == 0:
            # il faut sauter la premier ligne qui contient les en-têtes
            num_ligne += 1
        else:
            # récupérer toutes les valeurs d'une ligne, et convertir les valeurs numériques au type float
            echantillon = []
            for val in ligne:
                try:
                    echantillon.append(float(val))
                except:
                    echantillon.append(val)
            nom_base.append(echantillon)


def separer_base_donnees_tests(nom_base, taille_tests):
    """Sélectionner 'taille_tests' éléments de la base de données 'nom_base',
    les extraire de la base, et les sauvegarder dans une liste de 'donnees_test'."""

    # recopie toute la base de données, sans modifier celle d'entrée
    base_donnees = copy.deepcopy(nom_base)
    donnees_test = []
    for i in range(taille_tests):
        # tire aléatoirement un indice et extrait l'échantillon correspondant
        indice = random.randint(0, len(base_donnees) - 1)
        donnees_test.append(base_donnees[indice])
        base_donnees[indice:indice + 1] = []
    return base_donnees, donnees_test


def chercher_meilleur_k_classification(nom_base, nb_essais):
    """ Nécessite la fonction que vous allez implémenter à la question 5
    permet de comparer les scores des différentes valeurs de k."""

    taux_corrects = []
    for k in range(1, 10):
        taux = determiner_taux_bonne_classification_k(nom_base, k, nb_essais)
        print("pour k = " + str(k) + " : " + str(taux * 100) +
              "% corrects sur " + str(nb_essais) + " essais.")
        taux_corrects.append(taux)
    # le +1 vient du fait que les k sont de 1 à 9, mais les indices dans la liste sont de 0 à 8
    return taux_corrects.index(max(taux_corrects)) + 1


""" Fonctions de tests des fonctions que vous avez implémentées """


# Pour tester votre fonction distance avec des valeurs déjà calculées et attendues
def tester_votre_fonction_distance(nom_fichier):
    """ Teste la fonction distance(a,b) avec 10 valeurs déjà calculées."""

    base_verre = []
    recuperer_base(nom_fichier, base_verre)
    reponses_attendues = [
        0.6706713054842877,
        1.0517128885774871,
        1.197079780131633,
        2.1683173199511176,
        0.638905313798531,
        1.1951150572225258,
        0.8043009387039157,
        1.8254040648579701,
        1.7829750418892583,
        0.25495097567963915,
    ]
    for i in range(10):
        if abs(
                distance(base_verre[i], base_verre[i + 1]) -
                reponses_attendues[i]) > 10**(-10):
            print("Votre fonction distance(a,b) est incorrecte.")
            return 0
    print("Votre fonction distance(a,b) semble correcte.")
    return 1


# Pour tester votre fonction plus_proche_voisin avec des valeurs déjà calculées et attendues
def tester_votre_fonction_plus_proche_voisin(nom_fichier):
    """ Teste la fonction plus_proche_voisin(nom_fichier) avec 10 plus proches
    voisins déterminés en prenant en test les 10 premières valeurs de la base."""

    base_verre = []
    recuperer_base(nom_fichier, base_verre)
    reponses_attendues = [156, 1, 63, 60, 1, 28, 128, 56, 183, 56]
    for i in range(10):
        if plus_proche_voisin(base_verre[i],
                              base_verre[10:]) != reponses_attendues[i]:
            print(
                "Votre fonction plus_proche_voisin(donnee,nom_base) est incorrecte."
            )
            return 0
    print(
        "Votre fonction plus_proche_voisin(donnee,nom_base) semble correcte.")
    return 1


# Pour tester votre fonction k_plus_proches_voisins(k, donnee, nom_base) avec des valeurs attendues
def tester_votre_fonction_k_plus_proches_voisins(nom_fichier):
    """ Teste la fonction k_plus_proches_voisins(nom_fichier) avec k plus proches
    voisins déterminés en prenant en test les 8 premières valeurs de la base
    pour k = 1, 3 et 5."""

    base_verre = []
    recuperer_base(nom_fichier, base_verre)
    reponses_attendues = [
        [158, 83, 200, 37, 132],
        [3, 118, 111, 29, 68],
        [65, 180, 189, 17, 35],
        [62, 205, 166, 184, 89],
        [3, 111, 102, 118, 132],
        [30, 9, 79, 32, 123],
        [130, 110, 115, 56, 36],
        [1, 58, 40, 39, 183],
        [185, 198, 46, 57, 119],
        [58, 39, 183, 75, 151],
    ]
    for k in range(1, 6, 2):
        for i in range(8):
            if (k_plus_proches_voisins(
                    k, base_verre[i],
                    base_verre[8:])) != reponses_attendues[i][0:k]:
                print(
                    "Votre fonction k_plus_proches_voisins(k, donnee, nom_base) est incorrecte."
                )
                return 0
    print(
        "Votre fonction k_plus_proches_voisins(k, donnee, nom_base) semble correcte."
    )
    return 1


# Pour tester votre fonction prediction_classe(k, donnee, nom_base) avec des valeurs attendues
def tester_votre_fonction_prediction_classe(nom_fichier):
    """ Teste la fonction prediction_classe(nom_fichier) en prenant en test
    les 8 premières valeurs de la base
    pour k = 1, 3 et 5."""

    base_verre = []
    recuperer_base(nom_fichier, base_verre)
    reponses_attendues = [
        "fenetre de batiment",
        "fenetre de batiment",
        "fenetre de batiment",
        "verre de table",
        "fenetre de batiment",
        "fenetre de batiment",
        "fenetre de batiment",
        "fenetre de batiment",
    ]
    for k in range(1, 6, 2):
        for i in range(8):
            if (prediction_classe(k, base_verre[i],
                                  base_verre[8:])) != reponses_attendues[i]:
                print(
                    "Votre fonction prediction_classe(k, donnee, nom_base) est incorrecte."
                )
                return 0
    print(
        "Votre fonction prediction_classe(k, donnee, nom_base) semble correcte."
    )
    return 1


""" TP à réaliser """


# Question 1
def distance(a, b):
    """Calcule la distance euclidienne entre une donnee a et une donnee b en prenant en compte leurs
    3 coordonnées numériques.
    """
    pass


# Pour tester votre fonction distance, enlever le # suivant :
# tester_votre_fonction_distance(nom_fichier)


# Question 2
def plus_proche_voisin(donnee, nom_base):
    """Recherche dans la base de donnees 'nom_base' le plus proche voisin de 'donnee' au sens de la
    distance définie à la question 1 et renvoie son indice dans 'nom_base', distance ne doit pas non
    plus être nulle.
    """
    pass


# Si vous avez réussi à implémenter la fonction distance, pour tester votre fonction plus_proche_voisin, enlever le # suivant :
# tester_votre_fonction_plus_proche_voisin(nom_fichier)


# Question 3
def k_plus_proches_voisins(k, donnee, nom_base):
    """Recherche dans la base de donnees 'nom_base' les k plus proches voisins de 'donnee' en
    utilisant la fonction 'distance' précédente et renvoie la liste de leurs k indices dans
    'nom_base' Les distances ne doivent pas être nulles.
    """
    pass


# Pour tester votre fonction k_plus_proches_voisins, enlever le # suivant :
# tester_votre_fonction_k_plus_proches_voisins(nom_fichier)


# Question 4
def prediction_classe(k, donnee, nom_base):
    """Renvoie la classe (ici le type de verre) correspondant à la classe majoritaire des k plus
    proches voisins de donnee dans la base nom_base, la classe (type de verre) étant toujours
    stockée en dernière colonne de la base.
    """
    pass


# Pour tester votre fonction prediction_classe, enlever le # suivant :
# tester_votre_fonction_prediction_classe(nom_fichier)


# Question 5
def determiner_taux_bonne_classification_k(nom_base, k, nb_essais):
    """ Pour le nombre d'essais (nb_essais) demandés, récupère 1 échantillon
    de la base, et teste la prédiction de classe en utilisant la fonction
    prediction_classe puis totalise le nombre d'erreurs
    en comparant la prédiction avec la vraie classe."""
    pass


# Après avoir écrit votre fonction, vous pouver tester le meilleur k, avec :
"""base_verre = []
recuperer_base(nom_fichier, base_verre)
print('meilleur k = '+str(chercher_meilleur_k_classification(base_verre,5000)))
"""

# Question 6 :
"""base_verre = []
recuperer_base(nom_fichier, base_verre)
donnee = [2.09,1.04,14.46]"""
### A faire ###
