import random
import time
import math
import hashlib

# --- PARAMETRI ORGANIZZATI IN DIZIONARI ---

# SOURCE parameters
parameters_source = {
    'clockrate': 250e6,  # clock rate of source pulsed laser (250 MHz )
    'stab_overhead': 1e-3,  # 1ms realistic stabilization overhead
    'timeslot_duration_ns': 4.0 # Durata di un singolo timeslot in nanosecondi (4 ns)
}

# CHANNEL parameters
parameters_channels = {
    'alpha': 0.2,  # dB/km linear attenuation of standard optical fiber
    'length_alice_charlie_km': 150.0, # Lunghezza del canale tra Alice e Charlie in km
    'length_bob_charlie_km': 100.0    # Lunghezza del canale tra Bob e Charlie in km
}

# DETECTORS parameters (SPAD = Single-Photon Avalanche Diode, presumo)
parameters_SPAD = {
    'pDC': 200 / parameters_source['clockrate'], # pDC: detector Dark Counts (prob. per timeslot)
    'etadet': 0.1,  # etadet: detection efficiency (non ancora usata esplicitamente, ma utile)
    'detector_error_rate': 0.02 # Tasso di errore del rivelatore (e.g., polarizzazione, misallineamento)
}

# PROTOCOLS parameters (Common to all protocols or general security parameters)
parameters_protocols_common = {
    'intensity_vacuum_pulse': 0.0, # Intensità quando Alice/Bob non invia (vacuum state "n")
    'fError': 1.15,  # Realistic error correction (in)efficiency as in Lucamarini 2018
    'qber_sample_fraction': 0.1, # Frazione dei bits in Base Z usati per stimare il QBER
    'confidence_delta': 1e-10, # Probabilità di insicurezza (epsilon_sec) per i limiti statistici
    
    # Alice e Bob devono costruire ed inviare un Vacum o Coherent State, nella forma: |√μk e^i(δ+γ)|
    # Quindi insime intensità dovremmo codificare una GLOBAL PHASE RANDOMICA PRIVATA γ in (0,2pi],
    # Mentre δ può essere qualsiasi Ref, -->PHASE RANDOMICA INTRINSECA NEI FOTONI GENERATI DAL LASER ATTENUATO--> (non va quindi codificata ).
    'phase_global_range_start': 0.0, # Inizio range per la fase globale random (gamma)
    'phase_global_range_end': 2 * math.pi, # Fine range per la fase globale random (gamma)
    'phase_matching_tolerance': math.pi / 16, # Tolleranza per il matching delle fasi globali nel sifting (LAMDA CINESI SOTTO)
    'calibrated_phi_offset': math.pi / 2, # Esempio di valore per phi. Questo potrebbe essere variabile in un modello più complesso.( DELTA PHI CINESI SOTTO)
    'sigma_phi_noise': 0.01 # Deviazione standard del rumore di fase (SigmaPhi PER BERTAINA)
}

# PROTOCOLS parameters (SNS specific)
parameters_protocols_SNS = {
    'pZ_SNS': 0.8, # Probabilità di scegliere la Signal window (Base Z)
    'epsilon_A': 0.60, # Alice: probabilità di inviare (bit 1)
    'epsilon_B': 0.40, # Bob: probabilità di inviare (bit 0)
    'intensity_A_signal_pulse': 0.1200, # Intensità Signal di Alice
    'intensity_B_signal_pulse': 0.0800  # Intensità Signal di Bob
}


# --- CALCOLO E PARAMETRI DERIVATI PER IMPLMENTAZIONE ASIMMETRICA DEL SNS PROTOCOLLO (Decoy states e Asymmetry Factor) ---

# Intensità degli stati Decoy (u, v, w) per Alice (mu_Ak)--> fissi e scelti da noi.
# w è il vacuum state (intensità 0), corrispondente a "n"
parameters_decoy = {
    'intensity_A_u_decoy': 0.01,
    'intensity_A_v_decoy': 0.05,
    'intensity_A_w_decoy': parameters_protocols_common['intensity_vacuum_pulse']
}

#I decoy state di BoB vanno determinati con un fattore di assimmetria che dipende da : # (Eps A) != (Eps B )  ed    # (mu'_A) != (mu'_B)
# Pre-calcola il fattore costante di asimmetria tra Alice e Bob
numerator_term_constant = parameters_protocols_SNS['epsilon_A'] * \
                          (1 - parameters_protocols_SNS['epsilon_B']) * \
                          parameters_protocols_SNS['intensity_A_signal_pulse'] * \
                          math.exp(-parameters_protocols_SNS['intensity_A_signal_pulse'])

denominator_term_constant = parameters_protocols_SNS['epsilon_B'] * \
                            (1 - parameters_protocols_SNS['epsilon_A']) * \
                            parameters_protocols_SNS['intensity_B_signal_pulse'] * \
                            math.exp(-parameters_protocols_SNS['intensity_B_signal_pulse'])

ASYMMETRY_FACTOR = 1.0 # Default fallback, per evitare errori di divisione per zero in caso di parametri problematici

if denominator_term_constant == 0:
    print("ATTENZIONE: Il denominatore nel calcolo di ASYMMETRY_FACTOR è zero. Controlla EPSILON_B e INTENSITY_B_SIGNAL_PULSE.")
else:
    ASYMMETRY_FACTOR = numerator_term_constant / denominator_term_constant

# Calcola le intensità Decoy di Bob usando il vincolo
# mu_Bk = mu_Ak / ASYMMETRY_FACTOR
parameters_decoy['intensity_B_u_decoy'] = parameters_decoy['intensity_A_u_decoy'] / ASYMMETRY_FACTOR
parameters_decoy['intensity_B_v_decoy'] = parameters_decoy['intensity_A_v_decoy'] / ASYMMETRY_FACTOR
parameters_decoy['intensity_B_w_decoy'] = parameters_protocols_common['intensity_vacuum_pulse'] # w_B è il vacuum state per Bob (k=0, non soggetto al vincolo)

# Liste complete delle intensità decoy per Alice e Bob
parameters_decoy['decoy_intensities_alice'] = [
    parameters_decoy['intensity_A_u_decoy'],
    parameters_decoy['intensity_A_v_decoy'],
    parameters_decoy['intensity_A_w_decoy']
]
parameters_decoy['decoy_intensities_bob'] = [
    parameters_decoy['intensity_B_u_decoy'],
    parameters_decoy['intensity_B_v_decoy'],
    parameters_decoy['intensity_B_w_decoy']
]


# --- UNIFICAZIONE DI TUTTI I PARAMETRI ---
# Unisci tutti i dizionari in un unico dizionario 'global_parameters' per facile accesso
global_parameters = {
    **parameters_source,
    **parameters_channels,
    **parameters_SPAD, # Include i parametri del rivelatore qui
    **parameters_protocols_common,
    **parameters_protocols_SNS,
    **parameters_decoy, # Include i parametri decoy, che ora contengono anche quelli di Bob calcolati
    'ASYMMETRY_FACTOR': ASYMMETRY_FACTOR # Aggiungi il fattore di asimmetria calcolato
}

# --- PREPARAZIONE DEL DIZIONARIO  'var'  Post Processing : PER " RdB_SNS_AOPP_TF_QKD_err DI BERTAINA" ---> HP symmetric,-->  PER ADESSO METTIAMO QUELLI DI A, VEDIAMO DOPO COME GENERALIZZARE A e B con CINESI  !!!!!!
# Questo dizionario sarà passato come **var alla funzione
global_parameters['rdb_var_params'] = {
    'DetectorEfficiency': global_parameters['etadet'],
    'eps': global_parameters['epsilon_A'], # Assumo epsilon_A come riferimento per 'eps'    
    's': global_parameters['intensity_A_signal_pulse'],    
    'n': global_parameters['intensity_vacuum_pulse'],
    'decoy2': global_parameters['intensity_A_v_decoy'], # Mappa decoy2 -> v_decoy (assumo v sia l'intensità maggiore tra u e v)
    'decoy1': global_parameters['intensity_A_u_decoy'], # Mappa decoy1 -> u_decoy
    'decoy0': global_parameters['intensity_A_w_decoy'], # Mappa decoy0 -> w_decoy (vacuum)
    'pdc': global_parameters['pDC'],
    'edet': global_parameters['detector_error_rate'],
    'fError': global_parameters['fError'],
    'pZ': global_parameters['pZ_SNS']
}

# --- FINE DEFINIZIONE PARAMETRI ---

def choose_window():
    """Alice e Bob scelgono in modo indipendente e casuale una finestra (Signal o Decoy)."""
    # Accede al parametro 'pZ_SNS' dal dizionario globale dei parametri.
    return 'Signal' if random.random() < global_parameters['pZ_SNS'] else 'Decoy'

def prepare_signal_state_params(participant_name):
    """
    Alice e Bob preparano i parametri per la finestra Signal (Base Z).
    Nel protocollo SNS, il bit è codificato dalla decisione di inviare o meno (Send/No-Send).
    La fase globale randomica è inclusa per l'interferometria a Charlie.
    """
    
    is_sending = False
    intensity = global_parameters['intensity_vacuum_pulse']
    chosen_bit = None # Il bit è definito dalla decisione Send/No-Send dopo

    if participant_name == "Alice":
        # Alice invia (bit 1) con probabilità epsilon_A
        if random.random() < global_parameters['epsilon_A']:
            is_sending = True
            intensity = global_parameters['intensity_A_signal_pulse']
            chosen_bit = 1 # Alice invia -> bit 1
        else:
            is_sending = False
            intensity = global_parameters['intensity_vacuum_pulse']
            chosen_bit = 0 # Alice non invia -> bit 0
    elif participant_name == "Bob":
        # Bob invia (bit 0) con probabilità epsilon_B
        if random.random() < global_parameters['epsilon_B']:
            is_sending = True
            intensity = global_parameters['intensity_B_signal_pulse']
            chosen_bit = 0 # Bob invia -> bit 0
        else:
            is_sending = False
            intensity = global_parameters['intensity_vacuum_pulse']
            chosen_bit = 1 # Bob non invia -> bit 1
    else:
        raise ValueError("Nome del partecipante non valido. Deve essere 'Alice' o 'Bob'.")

    # La fase globale è sempre randomica per tutti gli stati, per l'interferometria.
    phase_global_phi = random.uniform(
        global_parameters['phase_global_range_start'],
        global_parameters['phase_global_range_end']
    )

    return {
        "window_type": "signal",
        "is_sending": is_sending,
        "intensity": intensity,
        "chosen_bit": chosen_bit,
        "phase_global_phi": phase_global_phi,
    }

def prepare_decoy_state_params(participant_name):
    """
    Alice e Bob preparano i parametri per la finestra Decoy (Base X).
    Scelgono un'intensità da un set predefinito.
    La fase globale randomica è inclusa per l'interferometria a Charlie.
    """

    if participant_name == "Alice":
        decoy_intensities = global_parameters['decoy_intensities_alice']
    elif participant_name == "Bob":
        decoy_intensities = global_parameters['decoy_intensities_bob']
    else:
        raise ValueError("Nome del partecipante non valido. Deve essere 'Alice' o 'Bob'.")

    # Scegli un'intensità decoy a caso
    intensity = random.choice(decoy_intensities)

    # La fase globale è sempre randomica per tutti gli stati, per l'interferometria.
    phase_global_phi = random.uniform(
        global_parameters['phase_global_range_start'],
        global_parameters['phase_global_range_end']
    )

    # In uno stato decoy, non c'è un bit di chiave da codificare direttamente
    # ma l'informazione dell'intensità è necessaria per l'analisi decoy state.
    return {
        "window_type": "decoy",
        "intensity": intensity,
        "phase_global_phi": phase_global_phi,
        "chosen_bit": None # Nessun bit associato direttamente allo stato decoy per la chiave
    }
def simulate_timeslot_preparation(participant_name, timeslot_index):
    """
    Simula la preparazione di un singolo timeslot (impulso fotonico)
    da parte di Alice o Bob, inclusa la scelta della finestra e dei parametri dello stato.

    Args:
    :param participant_name (str): "Alice" o "Bob".
    :param timeslot_index (int): L'indice del timeslot corrente.

    Returns:
        tuple:
            - fpga_params (tuple): (intensity_fpga, total_phase_prime_fpga)
              Parametri "ideali" del pulse come modulato dal dispositivo (es. FPGA).
            - pp_data (tuple): (window_type, public_info_for_pp, global_phase_for_pp)
              Dati che verranno annunciati pubblicamente per il post-processing (sifting e decoy state analysis).
              'public_info_for_pp' sarà il bit per Signal window, o l'intensità per Decoy window.
            - additional_info (dict): Dizionario con informazioni aggiuntive come is_sending, intensity per tracciamento.
    """
    # 1. Scegliere la finestra (Signal o Decoy)
    window_type = choose_window()

    # 2. Preparare i parametri dello stato in base alla finestra scelta
    if window_type == 'Signal':
        state_params = prepare_signal_state_params(participant_name)
        # Per Signal, l'informazione pubblica è il bit scelto
        public_info_for_pp = state_params['chosen_bit']
    elif window_type == 'Decoy':
        state_params = prepare_decoy_state_params(participant_name)
        # Per Decoy, l'informazione pubblica è l'intensità del decoy state
        public_info_for_pp = state_params['intensity'] # Intensità u, v, o w (vacuum)
    else:
        raise ValueError(f"Tipo di finestra non riconosciuto: {window_type}")

    # --- Estrazione dei parametri per i diversi output ---

    # Parametri "ideali" inviati (intensità e fase applicata al laser)
    intensity_fpga = state_params['intensity']
    # Nel SNS, la fase totale applicata al laser è la fase globale randomica (gamma)
    total_phase_prime_fpga = state_params['phase_global_phi']                                #NOTA IMPORTANTE !!!
    fpga_params = (intensity_fpga, total_phase_prime_fpga)                                   #--> (I,phi) -->FPGA

    # Dati per il post-processing (annunciati pubblicamente)
    global_phase_for_pp = state_params['phase_global_phi'] # La fase globale randomica che verrà rivelata
    pp_data = (window_type, public_info_for_pp, global_phase_for_pp)                       #--> ([X,Z...];[S,NS..] or [u,w,v,...], [RANDOM GLOBL PHI] -->Post-Processing
    
    # Informazioni aggiuntive per il tracciamento
    additional_info = {
        'is_sending': state_params.get('is_sending'), # 'is_sending' esiste solo per i segnali
        'intensity_actual': state_params['intensity'] # L'intensità effettiva usata
    }

    return fpga_params, pp_data, additional_info

    
# --- FUNZIONE MAIN ---
def main_simulation(num_timeslots):
    """
    Esegue la simulazione della preparazione dei timeslot per Alice e Bob
    per un dato numero di iterazioni.

    Args:
    :param num_timeslots (int): Il numero di timeslot da simulare.

    Returns:
        tuple:
            - simulation_results (dict): Un dizionario contenente i risultati dettagliati della simulazione per analisi.
              (Stessa struttura di prima)
            - fpga_commands_list (list): Una lista di tuple, dove ogni tupla contiene
              (alice_fpga_params, bob_fpga_params) per il timeslot corrispondente,
              pronta per l'invio alla FPGA.
    """
    simulation_results = {}
    fpga_commands_list = [] # Nuova lista per i comandi FPGA

    print(f"Avvio simulazione per {num_timeslots} timeslot...")
    start_time = time.time()

    for i in range(num_timeslots):
        # Simula la preparazione per Alice
        alice_fpga, alice_pp_data, alice_additional_info = simulate_timeslot_preparation("Alice", i)
        
        # Simula la preparazione per Bob
        bob_fpga, bob_pp_data, bob_additional_info = simulate_timeslot_preparation("Bob", i)

        # Aggiungi i dati dettagliati al dizionario dei risultati per l'analisi
        simulation_results[i] = {
            'alice_fpga': alice_fpga,
            'alice_pp_data': alice_pp_data,
            'alice_is_sending': alice_additional_info['is_sending'],
            'alice_intensity_actual': alice_additional_info['intensity_actual'],
            'bob_fpga': bob_fpga,
            'bob_pp_data': bob_pp_data,
            'bob_is_sending': bob_additional_info['is_sending'],
            'bob_intensity_actual': bob_additional_info['intensity_actual']
        }
        
        # Aggiungi i parametri FPGA alla lista specifica per l'hardware
        fpga_commands_list.append((alice_fpga, bob_fpga))

        # Stampa un feedback ogni tanto per simulazioni lunghe
        if (i + 1) % 10000 == 0:
            print(f"Simulati {i + 1}/{num_timeslots} timeslot...")

    end_time = time.time()
    print(f"Simulazione completata in {end_time - start_time:.2f} secondi.")
    
    return simulation_results, fpga_commands_list # Restituisci entrambi i risultati
ef save_fpga_commands_to_file(filename, fpga_commands):
    """
    Salva i comandi FPGA generati in un file di testo con l'header specificato.

    :param filename (str): Il nome del file in cui salvare i comandi FPGA.
    :param fpga_commands (list): Lista di tuple, dove ogni tupla contiene
                                 (alice_fpga_params, bob_fpga_params).
    """
    with open(filename, 'w') as f:
        f.write("INTENSITY ALICE    PHASE ALICE    INTENSITY BOB    PHASE BOB\n")
        f.write("-----------------------------------------------------------\n") # Separatore per chiarezza

        for alice_params, bob_params in fpga_commands:
            alice_intensity, alice_phase = alice_params
            bob_intensity, bob_phase = bob_params
            # Formatta la riga per allineare i valori
            f.write(f"{alice_intensity:<17.8f}{alice_phase:<15.8f}{bob_intensity:<17.8f}{bob_phase:<15.8f}\n")


def save_simulation_results_to_file(filename, global_params, detailed_results, fpga_commands):
    """
    Salva tutti i risultati della simulazione (parametri, risultati dettagliati per timeslot
    e riepilogo finale) in un file di testo.

    :param filename (str): Il nome del file in cui salvare i risultati.
    :param global_params (dict): Dizionario con tutti i parametri globali del protocollo.
    :param detailed_results (dict): Dizionario con i risultati dettagliati per ogni timeslot.
    :param fpga_commands (list): Lista dei comandi FPGA generati.
    """
    with open(filename, 'w') as f:
        f.write("=" * 50 + "\n")
        f.write("RIEPILOGO DEI PARAMETRI DEL PROTOCOLLO QKD SNS\n")
        f.write("=" * 50 + "\n\n")

        f.write("--- Probabilità di Finestra (Signal/Decoy) ---\n")
        f.write(f"Probabilità di scegliere la finestra Signal (pZ_SNS): {global_params['pZ_SNS']:.2f}\n")
        f.write(f"Probabilità di scegliere la finestra Decoy: {1 - global_params['pZ_SNS']:.2f}\n\n")

        f.write("--- Parametri di Alice ---\n")
        f.write(f"Intensità del pulso Signal di Alice (mu'_A): {global_params['intensity_A_signal_pulse']:.4f}\n")
        f.write(f"Intensità Decoy di Alice (u): {global_params['intensity_A_u_decoy']:.4f}\n")
        f.write(f"Intensità Decoy di Alice (v): {global_params['intensity_A_v_decoy']:.4f}\n")
        f.write(f"Intensità Vacuum (w) per Alice: {global_params['intensity_A_w_decoy']:.4f}\n")
        f.write(f"Probabilità di Alice di inviare (epsilon_A): {global_params['epsilon_A']:.2f} (corrisponde a bit 1)\n")
        f.write(f"Probabilità di Alice di NON inviare (1 - epsilon_A): {1 - global_params['epsilon_A']:.2f} (corrisponde a bit 0)\n\n")

        f.write("--- Parametri di Bob ---\n")
        f.write(f"Intensità del pulso Signal di Bob (mu'_B): {global_params['intensity_B_signal_pulse']:.4f}\n")
        f.write(f"Intensità Decoy di Bob (u): {global_params['intensity_B_u_decoy']:.4f}\n")
        f.write(f"Intensità Decoy di Bob (v): {global_params['intensity_B_v_decoy']:.4f}\n")
        f.write(f"Intensità Vacuum (w) per Bob: {global_params['intensity_B_w_decoy']:.4f}\n")
        f.write(f"Probabilità di Bob di inviare (epsilon_B): {global_params['epsilon_B']:.2f} (corrisponde a bit 0)\n")
        f.write(f"Probabilità di Bob di NON inviare (1 - epsilon_B): {1 - global_params['epsilon_B']:.2f} (corrisponde a bit 1)\n")
        f.write(f"\nFattore di asimmetria (ASYMMETRY_FACTOR): {global_params['ASYMMETRY_FACTOR']:.4f}\n\n")

        f.write("=" * 50 + "\n")
        f.write("INIZIO SIMULAZIONE DETTAGLIATA PER TIMESLOT\n")
        f.write("=" * 50 + "\n\n")

        # Scrivi i primi 5 risultati dettagliati
        f.write("Primi 5 risultati DETTAGLIATI della simulazione:\n")
        for i in range(min(5, len(detailed_results))):
            ts_data = detailed_results[i]
            f.write(f"\n--- Timeslot {i} ---\n")
            f.write(f"  Alice:\n")
            f.write(f"    Finestra Scelta: {ts_data['alice_pp_data'][0]}\n")
            f.write(f"    Informazione Pubblica (Bit chiave per Signal, Intensità per Decoy): {ts_data['alice_pp_data'][1]}\n")
            # Aggiustamento per la visualizzazione dello stato Send/No-Send per Alice
            alice_status_str = "Send (Bit 1)" if ts_data['alice_is_sending'] else "No-Send (Bit 0)"
            f.write(f"    Stato Send/No-Send (Bit Effettivo): {alice_status_str}\n")
            f.write(f"    Intensità Effettiva Inviata: {ts_data['alice_intensity_actual']:.3e}\n")
            f.write(f"    Parametri FPGA (Intensità, Fase Totale): {ts_data['alice_fpga']}\n")
            f.write(f"  Bob:\n")
            f.write(f"    Finestra Scelta: {ts_data['bob_pp_data'][0]}\n")
            f.write(f"    Informazione Pubblica (Bit chiave per Signal, Intensità per Decoy): {ts_data['bob_pp_data'][1]}\n")
            # Aggiustamento per la visualizzazione dello stato Send/No-Send per Bob
            bob_status_str = "Send (Bit 0)" if ts_data['bob_is_sending'] else "No-Send (Bit 1)"
            f.write(f"    Stato Send/No-Send (Bit Effettivo): {bob_status_str}\n")
            f.write(f"    Intensità Effettiva Inviata: {ts_data['bob_intensity_actual']:.3e}\n")
            f.write(f"    Parametri FPGA (Intensità, Fase Totale): {ts_data['bob_fpga']}\n")
            f.write("-" * 30 + "\n")

        f.write("\n" + "=" * 50 + "\n")
        f.write("RIEPILOGO FINALE DELLE SCELTE DELLA SIMULAZIONE\n")
        f.write("=" * 50 + "\n")

        # Inizializza i conteggi per il riepilogo finale
        alice_signal_send_bit1_count = 0 # Alice Send (Bit 1)
        alice_signal_nosend_bit0_count = 0 # Alice No-Send (Bit 0)
        alice_decoy_counts = {}

        bob_signal_send_bit0_count = 0 # Bob Send (Bit 0)
        bob_signal_nosend_bit1_count = 0 # Bob No-Send (Bit 1)
        bob_decoy_counts = {}

        # Popola i conteggi
        for ts_data in detailed_results.values():
            # Dati di Alice
            if ts_data['alice_pp_data'][0] == 'signal':
                if ts_data['alice_is_sending']:
                    alice_signal_send_bit1_count += 1
                else:
                    alice_signal_nosend_bit0_count += 1
            elif ts_data['alice_pp_data'][0] == 'decoy':
                decoy_intensity = ts_data['alice_intensity_actual']
                alice_decoy_counts[f"{decoy_intensity:.4f}"] = alice_decoy_counts.get(f"{decoy_intensity:.4f}", 0) + 1

            # Dati di Bob
            if ts_data['bob_pp_data'][0] == 'signal':
                if ts_data['bob_is_sending']:
                    bob_signal_send_bit0_count += 1
                else:
                    bob_signal_nosend_bit1_count += 1
            elif ts_data['bob_pp_data'][0] == 'decoy':
                decoy_intensity = ts_data['bob_intensity_actual']
                bob_decoy_counts[f"{decoy_intensity:.4f}"] = bob_decoy_counts.get(f"{decoy_intensity:.4f}", 0) + 1

        f.write(f"\n--- Alice - Conteggio Scelte per Finestra e Bit ---\n")
        f.write(f"  Signal (Bit 1 - Send): {alice_signal_send_bit1_count}\n")
        f.write(f"  Signal (Bit 0 - No-Send): {alice_signal_nosend_bit0_count}\n")
        f.write(f"  Decoy per Intensità:\n")
        if not alice_decoy_counts:
            f.write("    Nessuno stato Decoy registrato per Alice.\n")
        else:
            for intensity_str, count in sorted(alice_decoy_counts.items()):
                f.write(f"    Intensità {intensity_str}: {count}\n")

        f.write(f"\n--- Bob - Conteggio Scelte per Finestra e Bit ---\n")
        f.write(f"  Signal (Bit 0 - Send): {bob_signal_send_bit0_count}\n")
        f.write(f"  Signal (Bit 1 - No-Send): {bob_signal_nosend_bit1_count}\n")
        f.write(f"  Decoy per Intensità:\n")
        if not bob_decoy_counts:
            f.write("    Nessuno stato Decoy registrato per Bob.\n")
        else:
            for intensity_str, count in sorted(bob_decoy_counts.items()):
                f.write(f"    Intensità {intensity_str}: {count}\n")

        f.write("\n" + "=" * 50 + "\n")
        f.write("FINE SIMULAZIONE\n")
        f.write("=" * 50 + "\n")


# --- ESEMPIO DI UTILIZZO ---
if __name__ == "__main__":
    # --- RIASSUNTO DEI PARAMETRI DEL PROTOCOLLO ---
    print("=" * 50)
    print("RIEPILOGO DEI PARAMETRI DEL PROTOCOLLO QKD SNS")
    print("=" * 50)

    print("\n--- Probabilità di Finestra (Signal/Decoy) ---")
    print(f"Probabilità di scegliere la finestra Signal (pZ_SNS): {global_parameters['pZ_SNS']:.2f}")
    print(f"Probabilità di scegliere la finestra Decoy: {1 - global_parameters['pZ_SNS']:.2f}")

    print("\n--- Parametri di Alice ---")
    print(f"Intensità del pulso Signal di Alice (mu'_A): {global_parameters['intensity_A_signal_pulse']:.4f}")
    print(f"Intensità Decoy di Alice (u): {global_parameters['intensity_A_u_decoy']:.4f}")
    print(f"Intensità Decoy di Alice (v): {global_parameters['intensity_A_v_decoy']:.4f}")
    print(f"Intensità Vacuum (w) per Alice: {global_parameters['intensity_A_w_decoy']:.4f}")
    print(f"Probabilità di Alice di inviare (epsilon_A): {global_parameters['epsilon_A']:.2f} (corrisponde a bit 1)")
    print(f"Probabilità di Alice di NON inviare (1 - epsilon_A): {1 - global_parameters['epsilon_A']:.2f} (corrisponde a bit 0)")

    print("\n--- Parametri di Bob ---")
    print(f"Intensità del pulso Signal di Bob (mu'_B): {global_parameters['intensity_B_signal_pulse']:.4f}")
    print(f"Intensità Decoy di Bob (u): {global_parameters['intensity_B_u_decoy']:.4f}")
    print(f"Intensità Decoy di Bob (v): {global_parameters['intensity_B_v_decoy']:.4f}")
    print(f"Intensità Vacuum (w) per Bob: {global_parameters['intensity_B_w_decoy']:.4f}")
    print(f"Probabilità di Bob di inviare (epsilon_B): {global_parameters['epsilon_B']:.2f} (corrisponde a bit 0)")
    print(f"Probabilità di Bob di NON inviare (1 - epsilon_B): {1 - global_parameters['epsilon_B']:.2f} (corrisponde a bit 1)")
    
    print(f"\nFattore di asimmetria (ASYMMETRY_FACTOR): {global_parameters['ASYMMETRY_FACTOR']:.4f}")

    print("\n" + "=" * 50)
    print("INIZIO SIMULAZIONE DETTAGLIATA PER TIMESLOT")
    print("=" * 50)

    # Nuovo parametro: tempo totale di simulazione in secondi
    TOTAL_SIMULATION_TIME_SECONDS = 0.00004 # Esempio: 40 microsecondi
    
    # Calcola la durata di un timeslot in secondi
    timeslot_duration_seconds = global_parameters['timeslot_duration_ns'] * 1e-9 # Converti ns in secondi

    # Calcola il numero di timeslot basato sul tempo totale e la durata del timeslot
    NUM_TIMESLOTS_TO_SIMULATE = math.ceil(TOTAL_SIMULATION_TIME_SECONDS / timeslot_duration_seconds)
    print(f"Tempo totale di simulazione richiesto: {TOTAL_SIMULATION_TIME_SECONDS} secondi")
    print(f"Durata di un singolo timeslot: {global_parameters['timeslot_duration_ns']} ns")
    print(f"Numero di timeslot da simulare (calcolato): {NUM_TIMESLOTS_TO_SIMULATE}")

    # Esegui la simulazione e ricevi entrambi gli output
    detailed_results, fpga_commands = main_simulation(NUM_TIMESLOTS_TO_SIMULATE)

    # Puoi ispezionare i primi risultati dettagliati o analizzarli ulteriormente
    print("\nPrimi 5 risultati DETTAGLIATI della simulazione (stampati anche su console):")
    for i in range(min(5, NUM_TIMESLOTS_TO_SIMULATE)):
        print(f"\n--- Timeslot {i} ---")
        print(f"  Alice:")
        print(f"    Finestra Scelta: {detailed_results[i]['alice_pp_data'][0]}")
        print(f"    Informazione Pubblica (Bit chiave per Signal, Intensità per Decoy): {detailed_results[i]['alice_pp_data'][1]}")
        # Aggiustamento per la visualizzazione dello stato Send/No-Send per Alice
        alice_status_str = "Send (Bit 1)" if detailed_results[i]['alice_is_sending'] else "No-Send (Bit 0)"
        print(f"    Stato Send/No-Send (Bit Effettivo): {alice_status_str}")
        print(f"    Intensità Effettiva Inviata: {detailed_results[i]['alice_intensity_actual']:.3e}")
        print(f"    Parametri FPGA (Intensità, Fase Totale): {detailed_results[i]['alice_fpga']}")
        print(f"  Bob:")
        print(f"    Finestra Scelta: {detailed_results[i]['bob_pp_data'][0]}")
        print(f"    Informazione Pubblica (Bit chiave per Signal, Intensità per Decoy): {detailed_results[i]['bob_pp_data'][1]}")
        # Aggiustamento per la visualizzazione dello stato Send/No-Send per Bob
        bob_status_str = "Send (Bit 0)" if detailed_results[i]['bob_is_sending'] else "No-Send (Bit 1)"
        print(f"    Stato Send/No-Send (Bit Effettivo): {bob_status_str}")
        print(f"    Intensità Effettiva Inviata: {detailed_results[i]['bob_intensity_actual']:.3e}")
        print(f"    Parametri FPGA (Intensità, Fase Totale): {detailed_results[i]['bob_fpga']}")
        print("-" * 30)

    print("\n" + "=" * 50)
    print("PARAMETRI PER FPGA (LISTA DI TUPLE)")
    print("=" * 50)
    print(f"Lunghezza totale della lista comandi FPGA: {len(fpga_commands)}")
    print("Esempio dei primi 5 comandi FPGA:")
    for i in range(min(5, NUM_TIMESLOTS_TO_SIMULATE)):
        print(f"Timeslot {i}: Alice_FPGA={fpga_commands[i][0]}, Bob_FPGA={fpga_commands[i][1]}")
    
    # --- SALVATAGGIO DEI RISULTATI SU FILE ---
    output_filename_detailed = "simulazione_qkd_sns_results.txt"
    output_filename_fpga = "fpga_commands_log.txt"

    save_simulation_results_to_file(output_filename_detailed, global_parameters, detailed_results, fpga_commands)
    save_fpga_commands_to_file(output_filename_fpga, fpga_commands)

    print(f"\nRisultati dettagliati della simulazione salvati in '{output_filename_detailed}'")
    print(f"Comandi FPGA salvati in '{output_filename_fpga}'")

    print("\n" + "=" * 50)
    print("FINE ESECUZIONE SCRIPT")
    print("=" * 50)