import time
import tkinter as tk
import random
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from math import log10

class Win:
    def __init__(self):
        # Initialisation de la fenêtre principale
        self.win = tk.Tk()
        self.win.title("Sonometre")  # Titre de la fenêtre
        self.win.geometry("700x720")  # Taille de la fenêtre
        try:
            self.win.iconbitmap('microphone.ico')  # Tente de définir une icône pour la fenêtre
            self.win.iconphoto('microphone.ico')
        except:
            pass  # Ignore les erreurs si l'icône n'est pas trouvée

        # Initialisation des variables pour le sonomètre
        self.int_db = 0  # Niveau de décibels
        self.int_led = 0  # Valeur LED
        self.int_maxi = 0  # Valeur maximale
        self.int_mini = 255  # Valeur minimale
        self.int_moyenne = 0  # Moyenne des valeurs
        self.lst_list_moy = []  # Liste pour stocker les valeurs pour le calcul de la moyenne
        self.lst_list_led_val = ["00", "01", "03", "07"]  # Valeurs possibles pour les LEDs
        self.lst_list_ab_val = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]  # Valeurs possibles pour AB
        self.int_taille_echantillon = 400  # Taille de l'échantillon
        self.bool_emul = True  # Indicateur d'émulation
        self.bool_start_and_stop = False  # Indicateur de démarrage/arrêt

        # Création du canevas pour dessiner les éléments graphiques
        self.canvas = tk.Canvas(self.win, width=700, height=720, bg="#669bbc")
        self.canvas.pack()

        # Création des LEDs (cercles) pour indiquer le niveau sonore
        self.led1 = self.canvas.create_oval(10, 20, 40, 50, fill="#005f00", outline="#003049", width=2)  # LED 1
        self.led2 = self.canvas.create_oval(50, 20, 80, 50, fill="#c9a227", outline="#003049", width=2)  # LED 2
        self.led3 = self.canvas.create_oval(90, 20, 120, 50, fill="#990000", outline="#003049", width=2)  # LED 3

        # Création d'un rectangle pour afficher la dose
        self.rect = self.canvas.create_rectangle(530, 20, 580, 280, outline="black", width=2)
        self.dose = self.canvas.create_rectangle(530, 280, 580, 280, fill="#00ff00")

        # Ajout d'une checkbox pour activer/désactiver l'émulation
        self.int_var_led = tk.IntVar(value=True)
        self.checkbutton_emul = tk.Checkbutton(self.win, text="Emuler", variable=self.int_var_led, command=self.toggle_leds)
        self.checkbutton_emul.place(x=20, y=60)

        # Bouton pour démarrer/arrêter la mesure
        self.button_start_and_stop = tk.Button(self.win, text="Start", command=self.start_and_stop, background="#00ff00")
        self.button_start_and_stop.place(x=200, y=60)

        # Création de listes déroulantes (OptionMenu) pour sélectionner les valeurs LED et AB
        self.lst_options_led_listbox = self.lst_list_led_val
        self.label_led_listbox = tk.Label(self.win, text="", bg="#669bbc")
        self.label_led_listbox.place(x=80, y=200)
        self.str_option_select_listbox = tk.StringVar(value=self.lst_options_led_listbox[0])
        self.dropdown_led = tk.OptionMenu(self.win, self.str_option_select_listbox, *self.lst_options_led_listbox)
        self.dropdown_led.place(x=170, y=120)

        # Création de listes déroulantes (OptionMenu) pour sélectionner les valeurs LED et AB (bis)
        self.label_led_listbox_bis = tk.Label(self.win, text="", bg="#669bbc")
        self.label_led_listbox_bis.place(x=80, y=250)
        self.str_option_select_listbox_bis = tk.StringVar(value=self.lst_options_led_listbox[0])
        self.dropdown_bis = tk.OptionMenu(self.win, self.str_option_select_listbox_bis, *self.lst_options_led_listbox)
        self.dropdown_bis.place(x=170, y=170)

        # Liste déroulante pour la sélection de valeurs AB
        self.lst_options_ab_listbox = self.lst_list_ab_val
        self.label_ab_listbox_gauche = tk.Label(self.win, text="", bg="#669bbc")
        self.label_ab_listbox_gauche.place(x=80, y=200)
        self.str_option_select_ab_listbox_gauche = tk.StringVar(value=self.lst_options_ab_listbox[0])
        self.dropdown_ab_gauche = tk.OptionMenu(self.win, self.str_option_select_ab_listbox_gauche, *self.lst_options_ab_listbox)
        self.dropdown_ab_gauche.place(x=15, y=120)

        # Liste déroulante pour la sélection de valeurs AB (bis)
        self.label_ab_listbox_bis_gauche = tk.Label(self.win, text="", bg="#669bbc")
        self.label_ab_listbox_bis_gauche.place(x=80, y=250)
        self.str_option_select_ab_listbox_bis_gauche = tk.StringVar(value=self.lst_options_ab_listbox[0])
        self.dropdown_bis_ab_gauche = tk.OptionMenu(self.win, self.str_option_select_ab_listbox_bis_gauche, *self.lst_options_ab_listbox)
        self.dropdown_bis_ab_gauche.place(x=15, y=170)

        # Liste déroulante pour la sélection de valeurs AB à droite
        self.label_ab_listbox_droite = tk.Label(self.win, text="", bg="#669bbc")
        self.label_ab_listbox_droite.place(x=80, y=200)
        self.str_option_select_ab_listbox_droite = tk.StringVar(value=self.lst_options_ab_listbox[0])
        self.dropdown_ab_droite = tk.OptionMenu(self.win, self.str_option_select_ab_listbox_droite, *self.lst_options_ab_listbox)
        self.dropdown_ab_droite.place(x=70, y=120)

        # Liste déroulante pour la sélection de valeurs AB (bis) à droite
        self.label_ab_listbox_bis_droite = tk.Label(self.win, text="", bg="#669bbc")
        self.label_ab_listbox_bis_droite.place(x=80, y=250)
        self.str_option_select_ab_listbox_bis_droite = tk.StringVar(value=self.lst_options_ab_listbox[0])
        self.dropdown_bis_ab_droite = tk.OptionMenu(self.win, self.str_option_select_ab_listbox_bis_droite, *self.lst_options_ab_listbox)
        self.dropdown_bis_ab_droite.place(x=70, y=170)

        # Textes pour afficher les niveaux de décibels
        self.label_db_text = self.canvas.create_text(300, 35, text="0dB", font=("Arial", 20), fill="black")
        self.label_db_min_text = self.canvas.create_text(475, 50, text="min : 0dB", font=("Arial", 13), fill="black")
        self.label_db_max_text = self.canvas.create_text(475, 75, text="max : 0dB", font=("Arial", 13), fill="black")
        self.label_db_moy_text = self.canvas.create_text(475, 100, text="moy : 0dB", font=("Arial", 13), fill="black")
        self.label_de = self.canvas.create_text(150, 135, text="à", font=("Arial", 13), fill="black")
        self.label_dede = self.canvas.create_text(150, 185, text="à", font=("Arial", 13), fill="black")

        # Création de la figure Matplotlib pour le graphique en temps réel
        self.fig, self.ax = plt.subplots(figsize=(6, 4))
        self.lst_xdata, self.lst_ydata = [], []  # Données pour le graphique
        self.float_time = 0  # Temps initial
        self.ln, = self.ax.plot([], [], 'r-', animated=True)  # Ligne du graphique
        self.ax.set_facecolor("#669bbc")  # Couleur de fond de l'axe
        self.fig.patch.set_facecolor("#669bbc")  # Couleur de fond de la figure
        self.ln.set_color('#003566')  # Couleur de la ligne

        # Limites de l'axe X (temps) et de l'axe Y (dB)
        self.ax.set_xlim(0, 10)  # Plage initiale de l'axe X (10 secondes visibles)
        self.ax.set_ylim(0, 300)  # Plage de l'axe Y pour les dB

        # Intégration du graphique dans Tkinter via FigureCanvasTkAgg
        self.canvas_fig = FigureCanvasTkAgg(self.fig, master=self.canvas)
        self.canvas_fig.draw()
        self.canvas_fig.get_tk_widget().place(x=42, y=281)  # Positionner le graphique en bas de l'écran

        # Animation du graphique en temps réel
        self.ani = animation.FuncAnimation(self.fig, self.update, frames=np.linspace(0, 2 * np.pi, 128),
                                           init_func=self.init, blit=True, interval=100)  # Mise à jour chaque seconde

    def toggle_leds(self):
        # Fonction pour activer ou désactiver l'émulation des LEDs
        if self.int_var_led.get():
            self.bool_emul = True  # Activer l'émulation
        else:
            self.bool_emul = False  # Désactiver l'émulation

    def change_all_val_and_update(self, ab, led):
        # Met à jour toutes les valeurs et les affiche
        self.change_db(ab)  # Met à jour le niveau de décibels
        self.change_led(led)  # Met à jour la valeur LED
        self.change_min(ab)  # Met à jour la valeur minimale
        self.change_max(ab)  # Met à jour la valeur maximale
        self.cal_moy(ab)  # Calcule la moyenne

        self.col_led_dose()  # Met à jour les couleurs des LEDs
        self.dose_update()  # Met à jour l'affichage de la dose
        self.text_update()  # Met à jour les textes affichés

    def gen_data(self):
        # Génère des données aléatoires pour simuler les niveaux de décibels
        try:
            self.lst_ab_gauche = self.lst_list_ab_val[self.lst_list_ab_val.index(self.str_option_select_ab_listbox_gauche.get()) : self.lst_list_ab_val.index(self.str_option_select_ab_listbox_bis_gauche.get()) + 1]
            self.lst_ab_droite = self.lst_list_ab_val[self.lst_list_ab_val.index(self.str_option_select_ab_listbox_droite.get()) : self.lst_list_ab_val.index(self.str_option_select_ab_listbox_bis_droite.get()) + 1]
            self.lst_led = self.lst_list_led_val[self.lst_list_led_val.index(self.str_option_select_listbox.get()) : self.lst_list_led_val.index(self.str_option_select_listbox_bis.get()) + 1]

            chaine = ""
            for _ in range(self.int_taille_echantillon):
                chaine += "ab" + random.choice(self.lst_ab_gauche) + random.choice(self.lst_ab_droite) + "dc" + random.choice(self.lst_led)
            return chaine  # Retourne la chaîne générée
        except:
            return "ab00cd00"  # Valeur par défaut en cas d'erreur
        time.sleep(500)  # Pause de 500 ms

    def start_and_stop(self):
        # Fonction pour démarrer ou arrêter la mesure
        if self.bool_start_and_stop:
            self.button_start_and_stop.config(text="Start", bg="#00ff00", fg="black")  # Met à jour le bouton pour indiquer le démarrage
            print(str(self.bool_start_and_stop))
            self.bool_start_and_stop = False  # Arrête la mesure
        else:
            self.button_start_and_stop.config(text="Stop", bg="#ff0000", fg="black")  # Met à jour le bouton pour indiquer l'arrêt
            print("false" + str(self.bool_start_and_stop))
            self.bool_start_and_stop = True  # Démarre la mesure

    def update(self, frame):
        # Met à jour les données du graphique en temps réel
        self.float_time += 0.1  # Incrémenter le temps (en secondes)

        # Générer de nouvelles données de dB basées sur le temps
        dB_value = self.int_db  # Utiliser la fonction get_data pour obtenir la valeur du signal en dB

        # Ajouter les nouvelles données (temps et dB)
        self.lst_xdata.append(self.float_time)
        self.lst_ydata.append(dB_value)

        # Mettre à jour les limites de l'axe X pour faire défiler le temps
        if self.float_time > 10:  # Décaler l'axe X pour ne garder que les 10 dernières secondes visibles
            self.ax.set_xlim(self.float_time - 10, self.float_time)

        # Mettre à jour la courbe avec les nouvelles données
        self.ln.set_data(self.lst_xdata, self.lst_ydata)
        self.canvas_fig.draw()  # Redessiner le graphique

        return self.ln,  # Retourne la ligne mise à jour

    def change_db(self, val):
        # Met à jour le niveau de décibels en fonction de la valeur fournie
        try:
            self.int_db = int(40 * log10(val) - 10)  # Calcul du niveau en dB
        except:
            pass  # Ignore les erreurs

    def change_led(self, val):
        # Met à jour la valeur LED
        self.int_led = val

    def change_max(self, val):
        # Met à jour la valeur maximale
        self.int_maxi = max(self.int_maxi, val)

    def change_min(self, val):
        # Met à jour la valeur minimale
        self.int_mini = min(self.int_mini, val)

    def cal_moy(self, val):
        # Calcule la moyenne des valeurs et l'ajoute à la liste
        self.lst_list_moy.append(val)
        self.int_moyenne = int(sum(self.lst_list_moy) / len(self.lst_list_moy))  # Calcul de la moyenne

    def col_led_dose(self):
        # Met à jour les couleurs des LEDs en fonction de la valeur LED
        match int(self.int_led):
            case 0:
                self.led_update("#005f00", "#c9a227", "#990000")  # Couleurs pour LED 0
            case 1:
                self.led_update("#00ff00", "#c9a227", "#990000")  # Couleurs pour LED 1
            case 3:
                self.led_update("#00ff00", "#ffff00", "#990000")  # Couleurs pour LED 3
            case 7:
                self.led_update("#00ff00", "#ffff00", "#ff0000")  # Couleurs pour LED 7

    def led_update(self, l1=None, l2=None, l3=None):
        # Mise à jour des couleurs des LEDs
        if l1:
            self.canvas.itemconfig(self.led1, fill=l1)  # Met à jour la couleur de la LED 1
        if l2:
            self.canvas.itemconfig(self.led2, fill=l2)  # Met à jour la couleur de la LED 2
        if l3:
            self.canvas.itemconfig(self.led3, fill=l3)  # Met à jour la couleur de la LED 3

    def dose_update(self):
        # Met à jour la représentation de la dose sur le canevas
        self.canvas.coords(self.dose, 530, 280 - self.int_db, 580, 280)  # Ajuste la position du rectangle de dose
        if self.int_db <= 80:
            self.canvas.itemconfig(self.dose, fill="#00ff00")  # Couleur verte pour les niveaux bas
        elif self.int_db <= 170:
            self.canvas.itemconfig(self.dose, fill="#fcf300")  # Couleur jaune pour les niveaux moyens
        else:
            self.canvas.itemconfig(self.dose, fill="#ff0000")  # Couleur rouge pour les niveaux élevés

    def text_update(self):
        # Met à jour les textes affichés pour les niveaux de décibels
        self.canvas.itemconfig(self.label_db_text, text=str(self.int_db) + "dB")  # Affiche le niveau actuel
        self.canvas.itemconfig(self.label_db_min_text, text="min : " + str(self.int_mini) + "dB")  # Affiche le minimum
        self.canvas.itemconfig(self.label_db_max_text, text="max : " + str(self.int_maxi) + "dB")  # Affiche le maximum
        self.canvas.itemconfig(self.label_db_moy_text, text="moy : " + str(self.int_moyenne) + "dB")  # Affiche la moyenne

    def init(self):
        # Initialise le graphique
        self.ln.set_data([], [])  # Réinitialise les données de la ligne
        return self.ln,  # Retourne la ligne initialisée