import pandas as pd  # Importa la libreria pandas per la gestione dei dati.
import numpy as np  # Importa la libreria numpy per operazioni numeriche.
from sklearn.model_selection import KFold  # Importa KFold per la validazione incrociata.
from sklearn.preprocessing import MinMaxScaler  # Importa MinMaxScaler per la normalizzazione dei dati.
import tensorflow as tf  # Importa tensorflow per la costruzione del modello di rete neurale.
from tensorflow.keras.models import Sequential  # Importa Sequential per la costruzione del modello sequenziale.
from tensorflow.keras.layers import Dense, Dropout, BatchNormalization  # Importa le classi di layer.
from tensorflow.keras.callbacks import EarlyStopping  # Importa EarlyStopping per l'arresto anticipato.
import tkinter as tk  # Importa tkinter per l'interfaccia grafica.
from tkinter import messagebox, ttk, scrolledtext, filedialog  # Importa messagebox e ttk per widget specifici.
import os  # Importa os per interazioni con il sistema operativo.
import random  # Importa random per generare numeri casuali.
import matplotlib.pyplot as plt  # Importa matplotlib per la visualizzazione dei grafici.
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg  # Importa FigureCanvasTkAgg per integrare matplotlib con tkinter.
import logging  # Importa logging per il logging degli eventi.
import requests  # Importa requests per effettuare richieste HTTP.
from io import StringIO  # Importa StringIO per la gestione di stringhe come file.
import time  # Importa time per la gestione del tempo.
# Configurazione del logger
logger = logging.getLogger(__name__)  # Ottiene un logger con il nome del modulo.
logger.setLevel(logging.INFO)  # Imposta il livello di logging a INFO.
# Crea un handler per il box di testo
class TextHandler(logging.Handler):  # Definisce una classe per gestire il logging nel box di testo.
    def __init__(self, text_widget):  # Costruttore della classe.
        logging.Handler.__init__(self)  # Chiama il costruttore della classe base.
        self.text_widget = text_widget  # Memorizza il widget di testo.
    def emit(self, record):  # Metodo per emettere un messaggio di log.
        msg = self.format(record)  # Formatta il record di log.
        self.text_widget.config(state=tk.NORMAL)  # Abilita la modifica del widget di testo.
        self.text_widget.insert(tk.END, msg + '\n')  # Inserisce il messaggio di log.
        self.text_widget.config(state=tk.DISABLED)  # Disabilita la modifica del widget di testo.
        self.text_widget.see(tk.END)  # Fa scorrere il widget di testo fino alla fine.
# Imposta il seme per la riproducibilità
def set_seed(seed_value=42):  # Definisce una funzione per impostare il seme per la riproducibilità.
    os.environ['PYTHONHASHSEED'] = str(seed_value)  # Imposta una variabile d'ambiente per il seme di hash.
    random.seed(seed_value)  # Imposta il seme per il generatore di numeri casuali di Python.
    np.random.seed(seed_value)  # Imposta il seme per il generatore di numeri casuali di numpy.
    tf.random.set_seed(seed_value)  # Imposta il seme per il generatore di numeri casuali di tensorflow.
set_seed()  # Chiama la funzione per impostare il seme.
# Definizione dei link delle ruote
file_ruote = {  # Definisce un dizionario con i link dei file delle ruote.
    'BA': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/BARI.txt',  # Link per BARI.
    'CA': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/CAGLIARI.txt',  # Link per CAGLIARI.
    'FI': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/FIRENZE.txt',  # Link per FIRENZE.
    'GE': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/GENOVA.txt',  # Link per GENOVA.
    'MI': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/MILANO.txt',  # Link per MILANO.
    'NA': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/NAPOLI.txt',  # Link per NAPOLI.
    'PA': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/PALERMO.txt',  # Link per PALERMO.
    'RM': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/ROMA.txt',  # Link per ROMA.
    'TO': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/TORINO.txt',  # Link per TORINO.
    'VE': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/VENEZIA.txt',  # Link per VENEZIA.
    'NZ': 'https://raw.githubusercontent.com/Lottopython/estrazioni/refs/heads/main/NAZIONALE.txt'  # Link per NAZIONALE.
}
# Funzione per caricare i dati della ruota selezionata
def carica_dati(ruota, start_date=None, end_date=None):  # Definisce una funzione per caricare i dati.
    file_name = file_ruote.get(ruota)  # Ottiene il nome del file dalla ruota.
    if not file_name:  # Se il nome del file non esiste.
        logger.error("Ruota non trovata.")  # Logga un errore.
        messagebox.showerror("Errore", "Ruota non trovata.")  # Mostra un messaggio di errore.
        return None, None, None, None  # Restituisce None.
    try:  # Inizia un blocco try-except per la gestione degli errori.
        response = requests.get(file_name)  # Effettua una richiesta GET al link.
        response.raise_for_status()  # Solleva un'eccezione per errori HTTP.
        data = pd.read_csv(StringIO(response.text), header=None, sep="\t", encoding='utf-8')  # Legge i dati CSV.
        data.iloc[:, 0] = pd.to_datetime(data.iloc[:, 0], format='%Y/%m/%d')  # Converte la prima colonna in datetime.
        if start_date and end_date:  # Se sono state specificate date di inizio e fine.
            mask = (data.iloc[:, 0] >= start_date) & (data.iloc[:, 0] <= end_date)  # Crea una maschera per filtrare i dati.
            data = data.loc[mask]  # Applica la maschera ai dati.
        if data.empty:  # Se i dati sono vuoti.
            logger.error("Nessun dato trovato nell'intervallo di date specificato.")  # Logga un errore.
            messagebox.showerror("Errore", "Nessun dato trovato nell'intervallo di date specificato. Controlla le date di inizio e fine.")  # Mostra un messaggio di errore.
            return None, None, None, None  # Restituisce None.
        numeri = data.iloc[:, 2:].values  # Estrae i numeri dalle colonne.
        if numeri.size == 0:  # Se non ci sono numeri.
            logger.error("Nessun numero trovato nel file.")  # Logga un errore.
            return None, None, None, None  # Restituisce None.
        scaler = MinMaxScaler(feature_range=(0, 1))  # Crea un oggetto MinMaxScaler.
        numeri_normalizzati = scaler.fit_transform(numeri.astype(float))  # Normalizza i numeri.
        X = numeri_normalizzati[:-1]  # Crea i dati di input (X).
        y = numeri[:-1]  # Crea i dati di output (y).
        return X, y, scaler, data  # Restituisce X, y, scaler e data.
    except requests.exceptions.RequestException as e:  # Cattura le eccezioni di richiesta.
        logger.error(f"Errore durante il download dei dati dal link: {e}")  # Logga un errore.
        messagebox.showerror("Errore", f"Errore nel download dei dati dal link: {e}")  # Mostra un messaggio di errore.
        return None, None, None, None  # Restituisce None.
    except Exception as e:  # Cattura tutte le altre eccezioni.
        logger.error(f"Errore durante il caricamento dei dati: {e}")  # Logga un errore.
        messagebox.showerror("Errore", f"Errore durante il caricamento dei dati: {e}")  # Mostra un messaggio di errore.
        return None, None, None, None  # Restituisce None.
# Funzione di attivazione Swish
def swish(x):  # Definisce la funzione di attivazione Swish.
    return x * tf.keras.backend.sigmoid(x)  # Calcola e restituisce il valore di Swish.
# Variabili globali
optimizer_choice = None  # Inizializza la variabile per la scelta dell'ottimizzatore.
loss_function_choice = None  # Inizializza la variabile per la scelta della funzione di perdita.
patience = 10  # Imposta il valore di patience per EarlyStopping.
min_delta = 0.01  # Imposta il valore di min_delta per EarlyStopping.
progress_bar = None  # Inizializza la variabile per la barra di avanzamento.
fig = None  # Inizializza la variabile per la figura del grafico.
ax = None  # Inizializza la variabile per l'asse del grafico.
pulsanti_ruote = {}  # Inizializza il dizionario per i pulsanti delle ruote
entry_info = None  # Inizializza la variabile per il campo di input info
textbox = None  # Inizializza la variabile per il textbox di output
# Funzione per costruire il modello
def build_model(X_shape, y_shape, dense_layers, dropout_rates):  # Definisce la funzione per costruire il modello.
    model = Sequential()  # Crea un modello sequenziale.
    model.add(tf.keras.Input(shape=(X_shape,)))  # Aggiunge un layer di input.
    for i, (neurons, dropout) in enumerate(zip(dense_layers, dropout_rates)):  # Itera sui layer densi e sui dropout.
        logger.info(f"Layer {i+1}: Neuroni = {neurons}, Dropout = {dropout}")  # Logga le informazioni sul layer.
    for neurons, dropout in zip(dense_layers, dropout_rates):  # Itera sui layer densi e sui dropout.
        model.add(Dense(neurons, activation=swish))  # Aggiunge un layer denso con attivazione Swish.
        model.add(BatchNormalization())  # Aggiunge un layer di BatchNormalization.
        model.add(Dropout(dropout))  # Aggiunge un layer di Dropout.
    model.add(Dense(y_shape, activation=tf.keras.activations.relu))  # Aggiunge un layer denso di output.
    return model  # Restituisce il modello.
# Funzione per calcolare la prevedibilità delle ruote
def calcola_prevedibilita(history):  # Definisce la funzione per calcolare la prevedibilità.
    prevedibilita = {}  # Inizializza un dizionario per la prevedibilità.
    for ruota in history.keys():  # Itera sulle ruote.
        data_perdita = history[ruota]['val_loss']  # Ottiene i dati di perdita di validazione.
        if data_perdita:  # Se ci sono dati di perdita.
            prevedibilita[ruota] = np.mean(data_perdita)  # Calcola la media della perdita.
        else:  # Altrimenti.
            prevedibilita[ruota] = float('inf')  # Imposta la prevedibilità a infinito.
    return prevedibilita  # Restituisce il dizionario della prevedibilità.
# Funzione per mostrare la prevedibilità delle ruote
def mostra_prevedibilita(prevedibilita):  # Definisce la funzione per mostrare la prevedibilità.
    for child in frame_prevedibilita.winfo_children():  # Itera sui figli del frame della prevedibilità.
        child.destroy()  # Distrugge i figli.
    migliori_ruote = sorted(prevedibilita.items(), key=lambda x: x[1])[:2]  # Ordina le ruote per prevedibilità.
    ruote_string = ", ".join([ruota for ruota, _ in migliori_ruote])  # Crea una stringa con le migliori ruote.
    textbox.insert(tk.END, f"Le migliori ruote per la previsione sono: {ruote_string}\n")  # Inserisce il testo nel box di testo.
    fig, ax = plt.subplots(figsize=(10, 5))  # Crea un grafico.
    ax.bar(prevedibilita.keys(), prevedibilita.values())  # Crea un grafico a barre.
    ax.set_title('Prevedibilità delle Ruote (media perdite)')  # Imposta il titolo.
    ax.set_xlabel('Ruota')  # Imposta l'etichetta dell'asse x.
    ax.set_ylabel('Prevedibilità (Media Perdita)')  # Imposta l'etichetta dell'asse y.
    ax.grid(True)  # Aggiunge una griglia.
    canvas = FigureCanvasTkAgg(fig, master=frame_prevedibilita)  # Crea un canvas per il grafico.
    canvas.draw()  # Disegna il grafico.
    canvas.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH, expand=True)  # Inserisce il canvas nel frame.
# Funzione per aggiornare la barra di avanzamento e mostrare ogni selezione nel box di testo
def update_progress(value, max_value, text_to_append=None):  # Definisce la funzione per aggiornare la barra di avanzamento.
    if progress_bar:  # Se la barra di avanzamento esiste.
        progress_bar["maximum"] = max_value  # Imposta il valore massimo della barra.
        progress_bar["value"] = value  # Imposta il valore corrente della barra.
        root.update_idletasks()  # Aggiorna l'interfaccia grafica.
       
    if text_to_append and textbox:  # Se c'è del testo da aggiungere e il textbox esiste.
        textbox.insert(tk.END, text_to_append + "\n")  # Inserisce il testo nel box di testo.
# Funzione per gestire la selezione della ruota
def on_seleziona_ruota(ruota):  # Definisce la funzione per la selezione della ruota.
    global optimizer_choice, loss_function_choice, progress_bar, fig, ax  # Dichiara le variabili globali.
    start_date_str = entry_start_date.get()  # Ottiene la data di inizio.
    end_date_str = entry_end_date.get()  # Ottiene la data di fine.
    try:  # Inizia un blocco try-except.
        epochs = int(entry_epochs.get())  # Ottiene il numero di epoche.
        dense_layers = [  # Ottiene i layer densi.
            int(entry_neurons_layer1.get()),
            int(entry_neurons_layer2.get()),
            int(entry_neurons_layer3.get())
        ]
        dropout_rates = [  # Ottiene i tassi di dropout.
            float(entry_dropout_layer1.get()),
            float(entry_dropout_layer2.get()),
            float(entry_dropout_layer3.get())
        ]
    except ValueError:  # Cattura le eccezioni di valore.
        messagebox.showerror("Errore", "Inserisci valori validi per epoche, neuroni o dropout.")  # Mostra un messaggio di errore.
        return  # Esce dalla funzione.
    try:  # Inizia un blocco try-except.
        start_date = pd.to_datetime(start_date_str, format='%Y/%m/%d')  # Converte la data di inizio.
        end_date = pd.to_datetime(end_date_str, format='%Y/%m/%d')  # Converte la data di fine.
    except ValueError:  # Cattura le eccezioni di valore.
        messagebox.showerror("Errore", "Formato data non valido. Usa YYYY/MM/DD.")  # Mostra un messaggio di errore.
        return  # Esce dalla funzione.
    X, y, scaler, data = carica_dati(ruota, start_date, end_date)  # Carica i dati.
    if X is None or y is None or scaler is None or data is None:  # Se c'è un errore nel caricamento dei dati.
        return  # Esce dalla funzione.
    if X.size <= 0 or y.size <= 0:  # Se i dati sono vuoti.
        messagebox.showerror("Errore", "I dati caricati non contengono informazioni sufficienti per l'addestramento.")  # Mostra un messaggio di errore.
        return  # Esce dalla funzione.
    for btn in pulsanti_ruote.values():  # Itera sui pulsanti delle ruote.
        btn["bg"] = "SystemButtonFace"  # Imposta il colore di sfondo.
    pulsanti_ruote[ruota]["bg"] = "lightgreen"  # Imposta il colore di sfondo del pulsante selezionato.
    entry_info.delete(0, tk.END)  # Cancella il testo nel campo di input.
    entry_info.insert(0, f"Ruota: {ruota}, Periodo: {start_date.date()} - {end_date.date()}")  # Inserisce il testo nel campo di input.
    logger.info(f"Dimensione X: {X.shape}, Dimensione y: {y.shape}")  # Logga le dimensioni dei dati.
    k = min(5, len(X))  # Calcola il numero di fold per la validazione incrociata.
    kf = KFold(n_splits=k, shuffle=True, random_state=42)  # Crea un oggetto KFold.
    # Mostra informazioni di caricamento
    textbox.insert(tk.END, f"Caricamento dati per {ruota}...\n")  # Inserisce il testo nel box di testo.
    model = build_model(X.shape[1], y.shape[1], dense_layers, dropout_rates)  # Costruisce il modello.
    logger.info(f"Configurazione del Modello: Epoche = {epochs}, Ottimizzatore = {optimizer_choice}, Funzione di perdita = {loss_function_choice}")  # Logga la configurazione del modello.
    # Modifica qui per correggere l'errore di sintassi
    if optimizer_choice == '1':
        optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
    elif optimizer_choice == '2':
        optimizer = tf.keras.optimizers.RMSprop(learning_rate=0.001)
    else:
        optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
    # Fine modifica
    model.compile(optimizer=optimizer, loss=loss_function_choice, metrics=["mae"])  # Compila il modello.
    all_hist_loss = []  # Inizializza la lista per la perdita.
    all_hist_val_loss = []  # Inizializza la lista per la perdita di validazione.
    history_per_ruota = {}  # Inizializza il dizionario per la cronologia.
    # Mostra informazioni di addestramento
    textbox.insert(tk.END, f"Addestramento per {ruota}...\n")  # Inserisce il testo nel box di testo.
    progress_bar = ttk.Progressbar(root, orient="horizontal", length=200, mode="determinate", style="blue.Horizontal.TProgressbar")  # Crea la barra di avanzamento.
    progress_bar.pack(pady=10)  # Inserisce la barra di avanzamento.
    style = ttk.Style()  # Crea un oggetto Style.
    style.theme_use('clam')  # Usa il tema clam.
    style.configure("blue.Horizontal.TProgressbar", foreground='blue', background='lightblue')  # Configura la barra di avanzamento.
    style.map("blue.Horizontal.TProgressbar",
              background=[('active', 'blue')])
    fold_count = k  # Ottiene il numero di fold.
    epoch_count = epochs  # Ottiene il numero di epoche.
    total_steps = fold_count * epoch_count  # Calcola il numero totale di passi.
    current_step = 0  # Inizializza il passo corrente.
    for train_index, test_index in kf.split(X):  # Itera sui fold.
        X_train, X_test = X[train_index], X[test_index]  # Ottiene i dati di addestramento e test.
        y_train, y_test = y[train_index], y[test_index]
        noise = np.random.normal(0, 0.01 * np.abs(X_train), X_train.shape)  # Aggiunge rumore ai dati di addestramento.
        X_train_noisy = X_train + noise
        early_stopping = EarlyStopping(monitor='val_loss', patience=patience, min_delta=min_delta, restore_best_weights=True)  # Crea un oggetto EarlyStopping.
        history = model.fit(X_train_noisy, y_train, epochs=epochs, batch_size=64, validation_data=(X_test, y_test),
                            callbacks=[early_stopping], verbose=0)  # Addestra il modello.
        all_hist_loss.append(history.history['loss'])  # Aggiunge la perdita alla lista.
        all_hist_val_loss.append(history.history['val_loss'])  # Aggiunge la perdita di validazione alla lista.
        history_per_ruota[ruota] = history.history  # Memorizza la cronologia.
        logger.info(f"Fine addestramento fold, Con perdita train: {history.history['loss'][-1]}, Val: {history.history['val_loss'][-1]}")  # Logga la fine dell'addestramento.
        current_step += epoch_count  # Incrementa il passo corrente.
        update_progress(current_step, total_steps, f"Addestramento completato {current_step}/{total_steps}")  # Aggiorna la barra di avanzamento.
    y_pred = model.predict(X_test[-len(X_test):])  # Effettua la predizione.
    scaler_output = MinMaxScaler(feature_range=(1, 90))  # Crea un oggetto MinMaxScaler.
    y_pred_scaled = scaler_output.fit_transform(y_pred)  # Scala la predizione.
    numeri_interi = np.round(y_pred_scaled).astype(int)  # Arrotonda i numeri.
    # Mostra informazioni di predizione
    textbox.insert(tk.END, f"Predizione per {ruota}...\n")  # Inserisce il testo nel box di testo.
    update_textbox(numeri_interi)  # Aggiorna il box di testo con i numeri predetti.
    mostra_numeri_predetti(numeri_interi)  # Mostra i numeri predetti.
    mostra_migliori_risultati(history)  # Mostra i migliori risultati.
    prevedibilita = calcola_prevedibilita(history_per_ruota)  # Calcola la prevedibilità.
    mostra_prevedibilita(prevedibilita)  # Mostra la prevedibilità.
    mostra_grafico(all_hist_loss, all_hist_val_loss)  # Mostra il grafico.
    if 'history' in locals():  # Controlla se la variabile history esiste.
        loss_to_val_loss_ratio = [val_train / val_val if val_val != 0 else float('inf')  # Calcola il rapporto tra perdita e perdita di validazione.
                                   for val_train, val_val in zip(history.history['loss'], history.history['val_loss'])]
        min_ratio_epoch = np.argmin(loss_to_val_loss_ratio)  # Trova l'epoca con il rapporto minimo.
        plt.figure(figsize=(12, 6))  # Crea un grafico.
        plt.plot(history.history['loss'], label='Loss Train')  # Disegna la perdita di addestramento.
        plt.plot(history.history['val_loss'], label='Loss Val')  # Disegna la perdita di validazione.
        plt.plot(loss_to_val_loss_ratio, label='Ratio Loss/Val')  # Disegna il rapporto tra perdita e perdita di validazione.
        plt.scatter(min_ratio_epoch, loss_to_val_loss_ratio[min_ratio_epoch],
                    label='Optimal Solution (Early Stopping)', color='red')  # Disegna il punto ottimale.
        plt.title('Andamento della Perdita durante l\'Addestramento e Rapporto')  # Imposta il titolo del grafico.
        plt.xlabel('Epoche')  # Imposta l'etichetta dell'asse x.
        plt.ylabel('Perdita')  # Imposta l'etichetta dell'asse y.
        plt.axhline(0, color='black', linewidth=0.8, linestyle='--')  # Disegna una linea orizzontale.
        plt.legend()  # Aggiunge una legenda.
        plt.grid(True)  # Aggiunge una griglia.
        plt.show()  # Mostra il grafico.
    if progress_bar:  # Se la barra di avanzamento esiste.
        progress_bar.destroy()  # Distrugge la barra di avanzamento.
        progress_bar = None  # Imposta la barra di avanzamento a None.
    logger.info(f"Fine addestramento modello per {ruota}.")  # Logga la fine dell'addestramento.
def mostra_grafico(all_hist_loss, all_hist_val_loss):  # Definisce la funzione per mostrare il grafico.
    global fig, ax  # Dichiara le variabili globali.
    for child in frame_grafico.winfo_children():  # Itera sui figli del frame del grafico.
        child.destroy()  # Distrugge i figli.
    fig, ax = plt.subplots(figsize=(12, 10))  # Crea un grafico.
    for fold_idx in range(len(all_hist_loss)):  # Itera sui fold.
        ax.plot(all_hist_loss[fold_idx], label=f'Fold {fold_idx + 1} - Loss Train', linestyle='-')  # Disegna la perdita di addestramento.
        ax.plot(all_hist_val_loss[fold_idx], label=f'Fold {fold_idx + 1} - Loss Val', linestyle='--')  # Disegna la perdita di validazione.
    ax.set_title('Andamento della Perdita durante l\'Addestramento (K-Fold)')  # Imposta il titolo del grafico.
    ax.set_xlabel('Epoche')  # Imposta l'etichetta dell'asse x.
    ax.set_ylabel('Perdita')  # Imposta l'etichetta dell'asse y.
    ax.grid(True)  # Aggiunge una griglia.
    canvas = FigureCanvasTkAgg(fig, master=frame_grafico)  # Crea un canvas per il grafico.
    canvas.draw()  # Disegna il grafico.
    canvas.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH, expand=True)  # Inserisce il canvas nel frame.
    legend = ax.legend(loc='center left', bbox_to_anchor=(1, 0.5), fontsize='small')  # Aggiunge una legenda.
    fig.canvas.draw()  # Disegna il grafico.
    plt.close(fig)  # Chiude la figura.
def select_optimizer(opt):  # Definisce la funzione per selezionare l'ottimizzatore.
    global optimizer_choice  # Dichiara la variabile globale.
    optimizer_choice = opt  # Imposta l'ottimizzatore.
    logger.info(f"Ottimizzatore selezionato: {optimizer_choice}")  # Logga l'ottimizzatore selezionato.
def select_loss_function(loss_func):  # Definisce la funzione per selezionare la funzione di perdita.
    global loss_function_choice  # Dichiara la variabile globale.
    loss_function_choice = 'mean_squared_error' if loss_func == 'mse' else 'mean_absolute_error'  # Imposta la funzione di perdita.
    logger.info(f"Funzione di perdita selezionata: {loss_function_choice}")  # Logga la funzione di perdita selezionata.
def select_patience(patience_value):  # Definisce la funzione per selezionare il valore di patience.
    global patience  # Dichiara la variabile globale.
    patience = patience_value  # Imposta il valore di patience.
    logger.info(f"Patience impostato a: {patience}")  # Logga il valore di patience.
def select_min_delta(min_delta_value):  # Definisce la funzione per selezionare il valore di min_delta.
    global min_delta  # Dichiara la variabile globale.
    min_delta = min_delta_value  # Imposta il valore di min_delta.
    logger.info(f"Min Delta impostato a: {min_delta}")  # Logga il valore di min_delta.
def update_textbox(numeri):  # Definisce la funzione per aggiornare il box di testo con i numeri predetti.
    textbox.insert(tk.END, "Numeri Predetti:\n")  # Inserisce il testo nel box di testo.
    numeri_limitati = numeri.flatten()[:5]  # Estrae i primi 5 numeri.
    textbox.insert(tk.END, ", ".join(map(str, numeri_limitati)) + "\n")  # Inserisce i numeri nel box di testo.
def mostra_numeri_predetti(numeri):  # Definisce la funzione per mostrare i numeri predetti.
    textbox.insert(tk.END, "Numeri Predetti:\n")  # Inserisce il testo nel box di testo.
    numeri_limitati = numeri.flatten()[:5]  # Estrae i primi 5 numeri.
    textbox.insert(tk.END, ", ".join(map(str, numeri_limitati)) + "\n")  # Inserisce i numeri nel box di testo.
def mostra_migliori_risultati(history):  # Definisce la funzione per mostrare i migliori risultati.
    final_train_loss = history.history['loss'][-1]  # Ottiene la perdita di addestramento finale.
    final_val_loss = history.history['val_loss'][-1]  # Ottiene la perdita di validazione finale.
    textbox.insert(tk.END, f"Ultima Loss Train: {final_train_loss:.4f}\n")  # Inserisce il testo nel box di testo.
    textbox.insert(tk.END, f"Ultima Loss Val: {final_val_loss:.4f}\n")  # Inserisce il testo nel box di testo.
def salva_grafico(nome_file='grafico.png'):  # Definisce la funzione per salvare il grafico.
    if fig:  # Se la figura esiste.
        fig.savefig(nome_file)  # Salva la figura.
        messagebox.showinfo("Successo", f"Grafico salvato come {nome_file}")  # Mostra un messaggio di successo.
    else:  # Altrimenti.
        messagebox.showerror("Errore", "Nessun grafico disponibile per il salvataggio.")  # Mostra un messaggio di errore.
def salva_risultati(nome_file='risultati.txt'):  # Definisce la funzione per salvare i risultati.
    try:  # Inizia un blocco try-except.
        # Apre il file dialog per selezionare la posizione di salvataggio
        file_path = filedialog.asksaveasfilename(defaultextension=".txt",
                                                   filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
        if file_path:  # Se l'utente ha selezionato un file.
            with open(file_path, 'w') as file:  # Apre il file.
                file.write(textbox.get(1.0, tk.END))  # Scrive il contenuto del box di testo nel file.
            messagebox.showinfo("Successo", f"Risultati salvati in {file_path}")  # Mostra un messaggio di successo.
        else:
            messagebox.showinfo("Info", "Salvataggio cancellato dall'utente.")
    except Exception as e:  # Cattura tutte le eccezioni.
        messagebox.showerror("Errore", f"Errore nel salvataggio dei risultati: {e}")  # Mostra un messaggio di errore.
# Creazione della finestra principale
root = tk.Tk()  # Crea la finestra principale.
root.geometry("1200x1250")  # Imposta la dimensione della finestra.
# Crea una cornice per contenere l'etichetta del titolo e altri elementi, se necessario.
title_frame = tk.Frame(root)
title_frame.pack(pady=10)  # Aggiunge un po' di spazio verticale attorno alla cornice
# Crea l'etichetta del titolo con il colore desiderato.
title_label = tk.Label(title_frame, text="𝔼𝕞𝕡𝕒𝕥𝕙𝕚𝕩", fg="blue", font=("Arial", 24))  # Scegli il colore e il font
title_label.pack() # Posiziona l'etichetta all'interno della cornice
# Frame per i pulsanti di salvataggio
frame_salvataggio = tk.Frame(root)  # Crea un frame per i pulsanti di salvat  # Crea un frame per i pulsanti di salvataggio.
frame_salvataggio.pack(pady=10)  # Inserisce il frame nella finestra.
# Pulsanti per il salvataggio
btn_salva_grafico = tk.Button(frame_salvataggio, text="Salva Grafico", command=lambda: salva_grafico('grafico.png'), bg="#FFDDC1")  # Crea il pulsante per salvare il grafico.
btn_salva_grafico.pack(side=tk.LEFT, padx=10)  # Inserisce il pulsante nel frame.
btn_salva_risultati = tk.Button(frame_salvataggio, text="Salva Risultati", command=lambda: salva_risultati('risultati.txt'), bg="#FFDDC1")
btn_salva_risultati.pack(side=tk.LEFT, padx=10)
# Creazione del frame per i pulsanti
frame_pulsanti = tk.Frame(root)
frame_pulsanti.pack(pady=20)
# Creazione dei pulsanti per la selezione delle ruote
pulsanti_ruote = {}
for ruota in file_ruote.keys():
    btn = tk.Button(frame_pulsanti, text=ruota, command=lambda r=ruota: on_seleziona_ruota(r), bg="#ADD8E6", fg="black")
    btn.pack(side=tk.LEFT, padx=5)
    pulsanti_ruote[ruota] = btn
# Campo di input per la ruota selezionata e il periodo
entry_info = tk.Entry(frame_pulsanti, width=100, bg="#F0F0F0", fg="black")
entry_info.pack(pady=10)
# Campi di input per le date
frame_date = tk.Frame(frame_pulsanti)
frame_date.pack(pady=20)
label_start_date = tk.Label(frame_date, text="Data di Inizio (YYYY/MM/DD):", bg="#ADD8E6", fg="black")
label_start_date.pack(side=tk.LEFT)
entry_start_date = tk.Entry(frame_date, width=15, bg="#F0F0F0", fg="black")
entry_start_date.pack(side=tk.LEFT)
label_end_date = tk.Label(frame_date, text="Data di Fine (YYYY/MM/DD):", bg="#ADD8E6", fg="black")
label_end_date.pack(side=tk.LEFT)
entry_end_date = tk.Entry(frame_date, width=15, bg="#F0F0F0", fg="black")
entry_end_date.pack(side=tk.LEFT)
# Campo per specificare il numero di epoche
frame_epochs = tk.Frame(frame_pulsanti)
frame_epochs.pack(pady=20)
label_epochs = tk.Label(frame_epochs, text="Numero di Epoche:", bg="#ADD8E6", fg="black")
label_epochs.pack(side=tk.LEFT)
entry_epochs = tk.Entry(frame_epochs, width=5, bg="#F0F0F0", fg="black")
entry_epochs.insert(0, "100")
entry_epochs.pack(side=tk.LEFT)
# Sezione per le opzioni di Early Stopping
frame_early_stopping = tk.Frame(root)
frame_early_stopping.pack(pady=20)
label_early_stopping = tk.Label(frame_early_stopping, text="Opzioni Early Stopping:", bg="#ADD8E6", fg="black")
label_early_stopping.pack(side=tk.TOP)
btn_patience_5 = tk.Button(frame_early_stopping, text="Patience: 5", command=lambda: select_patience(5), bg="#C9E4CA")
btn_patience_5.pack(side=tk.LEFT, padx=5)
btn_patience_10 = tk.Button(frame_early_stopping, text="Patience: 10", command=lambda: select_patience(10), bg="#C9E4CA")
btn_patience_10.pack(side=tk.LEFT, padx=5)
btn_patience_15 = tk.Button(frame_early_stopping, text="Patience: 15", command=lambda: select_patience(15), bg="#C9E4CA")
btn_patience_15.pack(side=tk.LEFT, padx=5)
btn_min_delta_01 = tk.Button(frame_early_stopping, text="Min Delta: 0.01", command=lambda: select_min_delta(0.01), bg="#C9E4CA")
btn_min_delta_01.pack(side=tk.LEFT, padx=5)
btn_min_delta_05 = tk.Button(frame_early_stopping, text="Min Delta: 0.05", command=lambda: select_min_delta(0.05), bg="#C9E4CA")
btn_min_delta_05.pack(side=tk.LEFT, padx=5)
btn_min_delta_1 = tk.Button(frame_early_stopping, text="Min Delta: 0.10", command=lambda: select_min_delta(0.1), bg="#C9E4CA")
btn_min_delta_1.pack(side=tk.LEFT, padx=5)
# Aggiunta di campi per i neuroni e i tassi di dropout
frame_model_parameters = tk.Frame(frame_pulsanti)
frame_model_parameters.pack(pady=20)
label_layer1 = tk.Label(frame_model_parameters, text="Layer 1 Neuroni:", bg="#ADD8E6", fg="black")
label_layer1.pack(side=tk.LEFT)
entry_neurons_layer1 = tk.Entry(frame_model_parameters, width=5, bg="#F0F0F0", fg="black")
entry_neurons_layer1.insert(0, "512")
entry_neurons_layer1.pack(side=tk.LEFT)
label_dropout_layer1 = tk.Label(frame_model_parameters, text="Layer 1 Dropout:", bg="#ADD8E6", fg="black")
label_dropout_layer1.pack(side=tk.LEFT)
entry_dropout_layer1 = tk.Entry(frame_model_parameters, width=5, bg="#F0F0F0", fg="black")
entry_dropout_layer1.insert(0, "0.2")
entry_dropout_layer1.pack(side=tk.LEFT)
label_layer2 = tk.Label(frame_model_parameters, text="Layer 2 Neuroni:", bg="#ADD8E6", fg="black")
label_layer2.pack(side=tk.LEFT)
entry_neurons_layer2 = tk.Entry(frame_model_parameters, width=5, bg="#F0F0F0", fg="black")
entry_neurons_layer2.insert(0, "256")
entry_neurons_layer2.pack(side=tk.LEFT)
label_dropout_layer2 = tk.Label(frame_model_parameters, text="Layer 2 Dropout:", bg="#ADD8E6", fg="black")
label_dropout_layer2.pack(side=tk.LEFT)
entry_dropout_layer2 = tk.Entry(frame_model_parameters, width=5, bg="#F0F0F0", fg="black")
entry_dropout_layer2.insert(0, "0.2")  # Valore predefinito
entry_dropout_layer2.pack(side=tk.LEFT)
label_layer3 = tk.Label(frame_model_parameters, text="Layer 3 Neuroni:", bg="#ADD8E6", fg="black")
label_layer3.pack(side=tk.LEFT)
entry_neurons_layer3 = tk.Entry(frame_model_parameters, width=5, bg="#F0F0F0", fg="black")
entry_neurons_layer3.insert(0, "128")  # Valore predefinito
entry_neurons_layer3.pack(side=tk.LEFT)
label_dropout_layer3 = tk.Label(frame_model_parameters, text="Layer 3 Dropout:", bg="#ADD8E6", fg="black")
label_dropout_layer3.pack(side=tk.LEFT)
entry_dropout_layer3 = tk.Entry(frame_model_parameters, width=5, bg="#F0F0F0", fg="black")
entry_dropout_layer3.insert(0, "0.2")  # Valore predefinito
entry_dropout_layer3.pack(side=tk.LEFT)
# Frame per i pulsanti di ottimizzazione
frame_optimizer = tk.Frame(frame_pulsanti)
frame_optimizer.pack(side=tk.RIGHT, padx=20)
btn_optimizer_1 = tk.Button(frame_optimizer, text=" 1 (Adam)", command=lambda: select_optimizer('1'), bg="#C9E4CA", fg="black")
btn_optimizer_1.pack(pady=5)
btn_optimizer_2 = tk.Button(frame_optimizer, text=" 2 (RMSprop)", command=lambda: select_optimizer('2'), bg="#C9E4CA", fg="black")
btn_optimizer_2.pack(pady=5)
btn_optimizer_3 = tk.Button(frame_optimizer, text=" 3 (SGD)", command=lambda: select_optimizer('3'), bg="#C9E4CA", fg="black")
btn_optimizer_3.pack(pady=5)
# Frame per i pulsanti della funzione di perdita
frame_loss_function = tk.Frame(frame_pulsanti)
frame_loss_function.pack(side=tk.RIGHT, padx=20)
btn_loss_mse = tk.Button(frame_loss_function, text="Loss: MSE", command=lambda: select_loss_function('mse'), bg="#C9E4CA", fg="black")
btn_loss_mse.pack(pady=5)
btn_loss_mae = tk.Button(frame_loss_function, text="Loss: MAE", command=lambda: select_loss_function('mae'), bg="#C9E4CA", fg="black")
btn_loss_mae.pack(pady=5)
# Frame principale per il box di testo e la scrollbar
text_frame = tk.Frame(root)
text_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
# Scrollbar verticale
scrollbar = ttk.Scrollbar(text_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Scrollbar orizzontale
scrollbar_x = ttk.Scrollbar(text_frame, orient=tk.HORIZONTAL)
scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)
# Creazione del box di testo con scrollbar
textbox = tk.Text(text_frame, height=10, width=90, wrap=tk.NONE, yscrollcommand=scrollbar.set, xscrollcommand=scrollbar_x.set)
textbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Configura la scrollbar
scrollbar.config(command=textbox.yview)
scrollbar_x.config(command=textbox.xview)
# Frame per il grafico
frame_grafico = tk.Frame(root)
frame_grafico.pack(pady=10)
# Frame per la prevedibilità
frame_prevedibilita = tk.Frame(root)
frame_prevedibilita.pack(pady=10)
# Creazione e visualizzazione della finestra
root.mainloop()
# Apre il file di log alla chiusura
open_log_file()