import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import threading
import queue
import keyboard
from time import sleep
from datetime import datetime

class TransmisieCuantica:
    def __init__(self, fotoni_per_bit):
        self.fotoni_per_bit = fotoni_per_bit
        self.running = True
        self.bit_queue = queue.Queue()
        self.rezultate_queue = queue.Queue()
        
        # Configurare stil matplotlib
        plt.style.use('dark_background')
        
        # Creare fereastră mare și grafice cu dimensiuni fixe
        self.fig = plt.figure(figsize=(12, 8))
        self.fig.canvas.manager.window.geometry("1200x800")
        
        # Crearea subploturilor cu dimensiuni și spațiere explicite
        self.ax1 = self.fig.add_axes([0.1, 0.55, 0.35, 0.35])
        self.ax2 = self.fig.add_axes([0.55, 0.55, 0.35, 0.35])
        self.ax3 = self.fig.add_axes([0.1, 0.1, 0.35, 0.35])
        self.ax4 = self.fig.add_axes([0.55, 0.1, 0.35, 0.35])
        
        # Setare titlu principal
        self.fig.suptitle('Simulare Transmisie Cuantică Ghost', 
                         fontsize=16, color='white', y=0.95)
        
        # Buffer pentru istoric
        self.max_istoric = 50
        self.bits_transmisi = []
        self.bits_primiti = []
        
        # Parametri pentru distribuții
        self.x_points = np.linspace(-5, 5, 1000)
        self.k = 2 * np.pi / 0.702
        
        # Inițializare fișier log
        self.log_filename = f"transmisie_cuantica_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
        with open(self.log_filename, 'w') as f:
            f.write("Bit Transmis\tBit Primit\n")
            f.write("-" * 25 + "\n")
        
        # Inițializare grafice
        self.setup_plots()
        
    def setup_plots(self):
        """Inițializare grafice cu stilizare îmbunătățită"""
        # Configurare ax1 - Bitul curent
        self.ax1.set_title('Bit Curent', color='white', pad=20)
        self.ax1.set_facecolor('#1C1C1C')
        self.ax1.set_xlim(-1, 1)
        self.ax1.set_ylim(-0.5, 1.5)
        
        # Configurare ax2 - Distribuția fotonilor
        self.ax2.set_title('Distribuția Fotonilor', color='white', pad=20)
        self.ax2.set_facecolor('#1C1C1C')
        self.ax2.set_xlabel('Poziție')
        self.ax2.set_ylabel('Intensitate')
        
        # Configurare ax3 - Istoric
        self.ax3.set_title('Istoric Transmisie', color='white', pad=20)
        self.ax3.set_facecolor('#1C1C1C')
        self.ax3.set_xlabel('Număr Bit')
        self.ax3.set_ylabel('Valoare Bit')
        self.ax3.set_ylim(-0.5, 1.5)
        
        # Configurare ax4 - Statistici
        self.ax4.set_title('Statistici', color='white', pad=20)
        self.ax4.set_facecolor('#1C1C1C')

    def log_bits(self, bit_transmis, bit_primit):
        """Salvează biții în fișierul de log"""
        with open(self.log_filename, 'a') as f:
            f.write(f"{bit_transmis}\t\t{bit_primit}\n")

    def calculeaza_distributie(self, bit):
        """Calculează distribuția fotonilor cu parametri ajustați"""
        if bit == 0:  # Model interferență
            # Ajustăm parametrii pentru a face modelul de interferență mai distinct
            dist = np.exp(-self.x_points**2 / 4) * (np.cos(self.k * self.x_points))**2
        else:  # Model care-pe-unde
            # Mărim separarea între vârfuri pentru bit 1
            dist = (np.exp(-(self.x_points-2)**2 / 2) + np.exp(-(self.x_points+2)**2 / 2))
        return dist / np.sum(dist)

    def simuleaza_detectii(self, distributie):
        """Simulează detecțiile de fotoni"""
        return np.random.choice(len(self.x_points), size=self.fotoni_per_bit, p=distributie)

    def determina_bit_primit(self, detectii):
        """Determină bitul bazat pe detecții folosind o metodă îmbunătățită"""
        # Împărțim domeniul în două regiuni
        mijloc_idx = len(self.x_points) // 2
        detectii_stanga = np.sum(detectii < mijloc_idx)
        detectii_dreapta = np.sum(detectii >= mijloc_idx)
        
        # Pentru bit 1 ne așteptăm la două vârfuri distincte (stânga și dreapta)
        # Pentru bit 0 ne așteptăm la un pattern de interferență mai uniform
        ratio = abs(detectii_stanga - detectii_dreapta) / len(detectii)
        
        # Dacă ratio este mare (distribuție bimodală clară), atunci este probabil un 1
        # Dacă ratio este mic (distribuție mai uniformă), atunci este probabil un 0
        threshold = 0.15  # Această valoare poate fi ajustată pentru optimizare
        return 1 if ratio > threshold else 0

    def generator_biti(self):
        """Generează biți aleatori"""
        while self.running:
            self.bit_queue.put(np.random.randint(2))
            sleep(1)

    def transmisie(self):
        """Procesul de transmisie"""
        while self.running:
            if not self.bit_queue.empty():
                bit = self.bit_queue.get()
                distributie = self.calculeaza_distributie(bit)
                detectii = self.simuleaza_detectii(distributie)
                bit_primit = self.determina_bit_primit(detectii)
                
                # Salvare în fișier
                self.log_bits(bit, bit_primit)
                
                self.bits_transmisi.append(bit)
                self.bits_primiti.append(bit_primit)
                
                if len(self.bits_transmisi) > self.max_istoric:
                    self.bits_transmisi.pop(0)
                    self.bits_primiti.pop(0)
                
                self.rezultate_queue.put({
                    'bit_transmis': bit,
                    'bit_primit': bit_primit,
                    'distributie': distributie,
                    'detectii': detectii
                })
                sleep(0.1)

    def update_plots(self, frame):
        """Actualizează graficele"""
        if not self.rezultate_queue.empty():
            data = self.rezultate_queue.get()
            
            # Actualizare bit curent
            self.ax1.clear()
            self.ax1.set_title('Bit Curent', color='white', pad=20)
            self.ax1.bar([-0.2, 0.2], [data['bit_transmis'], data['bit_primit']], 
                        color=['#00ff00', '#ff9900'], 
                        width=0.3,
                        label=['Transmis', 'Primit'])
            self.ax1.set_ylim(-0.5, 1.5)
            self.ax1.set_xticks([-0.2, 0.2])
            self.ax1.set_xticklabels(['Transmis', 'Primit'])
            self.ax1.set_facecolor('#1C1C1C')
            
            # Actualizare distribuție
            self.ax2.clear()
            self.ax2.set_title('Distribuția Fotonilor', color='white', pad=20)
            self.ax2.plot(self.x_points, data['distributie'], color='#00ff00', 
                         label='Teoretic')
            self.ax2.hist(self.x_points[data['detectii']], bins=30, density=True, 
                         alpha=0.7, color='#ff9900', label='Detectat')
            self.ax2.legend()
            self.ax2.set_facecolor('#1C1C1C')
            
            # Actualizare istoric
            self.ax3.clear()
            self.ax3.set_title('Istoric Transmisie', color='white', pad=20)
            x = range(len(self.bits_transmisi))
            self.ax3.plot(x, self.bits_transmisi, 'go-', label='Transmiși', 
                         linewidth=2, markersize=8)
            self.ax3.plot(x, self.bits_primiti, 'yo--', label='Primiți', 
                         linewidth=2, markersize=8)
            self.ax3.set_ylim(-0.5, 1.5)
            self.ax3.legend()
            self.ax3.set_facecolor('#1C1C1C')
            
            # Actualizare statistici
            self.ax4.clear()
            self.ax4.set_title('Statistici', color='white', pad=20)
            total_bits = len(self.bits_transmisi)
            bits_corecti = sum(t == r for t, r in zip(self.bits_transmisi, self.bits_primiti))
            rata_succes = bits_corecti / total_bits * 100 if total_bits > 0 else 0
            
            stats_text = (
                f"Biți transmiși: {total_bits}\n"
                f"Biți corecți: {bits_corecti}\n"
                f"Rata de succes: {rata_succes:.1f}%\n"
                f"Fotoni per bit: {self.fotoni_per_bit}\n"
                f"Log file: {self.log_filename}\n\n"
                f"Apăsați 'q' pentru a opri"
            )
            
            self.ax4.text(0.1, 0.5, stats_text, color='white', fontsize=12, 
                         transform=self.ax4.transAxes)
            self.ax4.set_facecolor('#1C1C1C')
            
            self.fig.canvas.draw_idle()

    def run(self):
        """Pornește simularea"""
        # Pornire thread-uri
        threading.Thread(target=self.generator_biti, daemon=True).start()
        threading.Thread(target=self.transmisie, daemon=True).start()
        
        # Pornire animație
        ani = FuncAnimation(
            self.fig,
            self.update_plots,
            interval=100,
            cache_frame_data=False
        )
        
        plt.show()
        
        while self.running:
            if keyboard.is_pressed('q'):
                self.running = False
                break
            sleep(0.1)

def main():
    print("\nSimulare Transmisie Cuantică Ghost")
    print("---------------------------------")
    
    while True:
        try:
            fotoni = int(input("\nIntroduceți numărul de fotoni per bit (100-1000 recomandat): "))
            if 1 <= fotoni <= 100000:
                break
            print("Vă rugăm introduceți un număr între 1 și 10000.")
        except ValueError:
            print("Vă rugăm introduceți un număr valid.")
    
    print("\nPornire simulare...")
    print("Apăsați 'q' pentru a opri")
    
    transmisie = TransmisieCuantica(fotoni)
    transmisie.run()

if __name__ == "__main__":
    main()
