#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Quantum Eraser – CYBERPUNK VISUALIZER (v5.1 Fixed)
==================================================
Features:
1. "Matrix" Binary Waterfall (Live Memory Dump)
2. Real-time Graph Noise (Scanned failed keys visualizer)
3. Autonomous Attack Logic
4. BUG FIX: Matplotlib lines attribute error resolved.
"""

import tkinter as tk
from tkinter import ttk, messagebox
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import threading
import time
import random
from concurrent.futures import ProcessPoolExecutor
import multiprocessing

# ==================== LOGICĂ DE CALCUL (BACKEND) ====================

def generate_keystream_fast(key_int: int, length: int) -> np.ndarray:
    np.random.seed(key_int)
    return np.random.randint(0, 2, length, dtype=np.int8)

def evaluate_candidate_vectorized(args):
    """Calcul matematic pur - rulat pe nuclee separate."""
    key_int, key_len, s_bits, cum_shots = args
    I_guess = generate_keystream_fast(key_int, len(s_bits))
    
    scores = []
    idx_start = 0
    for idx_end in cum_shots[1:]:
        seg_I = I_guess[idx_start:idx_end]
        seg_S = s_bits[idx_start:idx_end]
        
        m0 = (seg_I == 0)
        c0 = np.sum(m0)
        c1 = len(seg_S) - c0
        
        if c0 > 0 and c1 > 0:
            p0 = np.sum(seg_S[m0]) / c0
            p1 = np.sum(seg_S[~m0]) / c1
            scores.append(abs(p0 - p1))
        idx_start = idx_end
        
    if not scores: return 0.0, key_int
    return np.mean(scores), key_int

# ==================== INTERFAȚA GRAFICĂ (FRONTEND) ====================

class CyberpunkApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("QUANTUM BREACH // VISUALIZER")
        self.geometry("1600x900")
        self.state('zoomed')
        
        # Stil Cyberpunk
        self.configure(bg="#050505")
        self.style = ttk.Style(self)
        self.style.theme_use('clam')
        self.style.configure(".", background="#050505", foreground="#00ff00", font=("Consolas", 10))
        self.style.configure("TLabel", background="#050505", foreground="#00ff41")
        self.style.configure("TButton", background="#111", foreground="#00ff41", bordercolor="#00ff41", borderwidth=1)
        self.style.map("TButton", background=[('active', '#222')])
        self.style.configure("TLabelframe", background="#050505", foreground="#00ff41", bordercolor="#003300")
        self.style.configure("TLabelframe.Label", background="#050505", foreground="#00ff41", font=("Consolas", 11, "bold"))

        # State variables
        self.running = False
        self.experiment_data = None
        self.true_key_len = 0
        
        # Visualizer vars
        self.current_scan_len = 8
        self.current_best_score = 0.0
        self.current_best_key = 0
        self.is_scanning = False
        
        self._setup_layout()
        
        # Start Visual Loop independent de logica
        self._animate_visuals()

    def _setup_layout(self):
        # HEADER
        top_bar = tk.Frame(self, bg="#001100", height=50)
        top_bar.pack(fill=tk.X)
        tk.Label(top_bar, text="QUANTUM DECRYPTION SUITE v5.1", font=("Consolas", 20, "bold"), bg="#001100", fg="#00ff41").pack(pady=5)

        # MAIN CONTAINER (3 Columns)
        container = tk.Frame(self, bg="#050505")
        container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # --- COL 1: CONTROLS & LOG (20%) ---
        col1 = tk.Frame(container, bg="#050505", width=300)
        col1.pack(side=tk.LEFT, fill=tk.Y, padx=5)
        
        ctrl_frame = ttk.LabelFrame(col1, text=" [ MISSION CONTROL ] ")
        ctrl_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(ctrl_frame, text="Encryption Strength:").pack(anchor="w", padx=5)
        self.scale_diff = tk.Scale(ctrl_frame, from_=8, to=32, orient=tk.HORIZONTAL, bg="#050505", fg="#00ff41", troughcolor="#222", highlightthickness=0)
        self.scale_diff.set(16)
        self.scale_diff.pack(fill=tk.X, padx=5, pady=5)
        
        self.btn_start = ttk.Button(ctrl_frame, text="INITIATE HACK", command=self.start_hack)
        self.btn_start.pack(fill=tk.X, padx=5, pady=10)
        
        # Stats
        stat_frame = ttk.LabelFrame(col1, text=" [ LIVE METRICS ] ")
        stat_frame.pack(fill=tk.X, pady=10)
        self.lbl_status = ttk.Label(stat_frame, text="SYSTEM: STANDBY")
        self.lbl_status.pack(anchor="w", padx=5)
        self.lbl_len = ttk.Label(stat_frame, text="Target Key: UNKNOWN")
        self.lbl_len.pack(anchor="w", padx=5)
        self.lbl_score = ttk.Label(stat_frame, text="Visibility: 0.00%")
        self.lbl_score.pack(anchor="w", padx=5)

        # Log
        ttk.Label(col1, text="[ SYSTEM EVENTS ]").pack(anchor="w", pady=(20,0))
        self.log_text = tk.Text(col1, height=20, width=35, bg="black", fg="#00aa00", font=("Consolas", 9), relief="flat")
        self.log_text.pack(fill=tk.BOTH, expand=True)

        # --- COL 2: GRAPHIC VISUALIZER (50%) ---
        col2 = tk.Frame(container, bg="#050505")
        col2.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10)
        
        self.fig = Figure(figsize=(5, 4), dpi=100, facecolor="#050505")
        self.ax = self.fig.add_subplot(111)
        self.ax.set_facecolor("black")
        # Green grid matrix style
        self.ax.grid(color='#003300', linestyle='-', linewidth=0.5)
        self.ax.spines['bottom'].set_color('#00ff41')
        self.ax.spines['top'].set_color('#00ff41')
        self.ax.spines['right'].set_color('#00ff41')
        self.ax.spines['left'].set_color('#00ff41')
        self.ax.tick_params(axis='x', colors='#00ff41')
        self.ax.tick_params(axis='y', colors='#00ff41')
        self.ax.set_title("INTERFERENCE PATTERN RECONSTRUCTION", color="#00ff41")
        
        self.canvas = FigureCanvasTkAgg(self.fig, col2)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # --- COL 3: MEMORY DUMP (MATRIX RAIN) (30%) ---
        col3 = ttk.LabelFrame(container, text=" [ LIVE MEMORY DUMP ] ")
        col3.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
        
        self.matrix_text = tk.Text(col3, bg="black", fg="#005500", font=("Consolas", 10), relief="flat", state="normal")
        self.matrix_text.pack(fill=tk.BOTH, expand=True)
        # Tag pentru cheile "respinse" (rosu inchis) si "acceptate" (verde)
        self.matrix_text.tag_config("fail", foreground="#550000")
        self.matrix_text.tag_config("scan", foreground="#005500")
        self.matrix_text.tag_config("hit", foreground="#00ff41", background="#002200")

    def log(self, msg):
        self.log_text.insert(tk.END, f"> {msg}\n")
        self.log_text.see(tk.END)

    def _animate_visuals(self):
        """Aceasta functie ruleaza CONTINUU pentru a crea efecte vizuale."""
        if self.is_scanning and self.running:
            # 1. MATRIX RAIN EFFECT
            lines_to_add = 2
            for _ in range(lines_to_add):
                fake_bin = "".join(str(random.randint(0,1)) for _ in range(self.current_scan_len))
                self.matrix_text.insert("1.0", f"0x{random.randint(100,999)}: {fake_bin} [REJECTED]\n", "fail")
            self.matrix_text.delete("50.0", tk.END)
            
            # 2. GRAPH NOISE (Ghost Lines)
            if self.experiment_data:
                phis = self.experiment_data[2]
                noise = np.random.rand(len(phis)) * 0.3 + 0.35 
                
                # === FIX AICI: Stergem manual liniile vechi (inafara de prima) ===
                lines = self.ax.lines
                # Pastram doar index 0 (Linia Verde Principala) daca exista
                while len(lines) > 1:
                    lines[-1].remove() 
                
                # Adaugam linia fantoma (Red Ghost)
                self.ax.plot(phis, noise, color='#ff0000', alpha=0.15, linewidth=1) 
                self.canvas.draw_idle()

        self.after(50, self._animate_visuals) # 20 FPS

    def start_hack(self):
        if self.running: return
        self.running = True
        self.btn_start.config(state="disabled")
        
        bits = self.scale_diff.get()
        self.log(f"Generating {bits}-bit target system...")
        
        # Generate Data
        threading.Thread(target=self._generate_data, args=(bits,), daemon=True).start()

    def _generate_data(self, bits):
        true_key = random.getrandbits(bits)
        n_angles = 16
        shots = 2000
        phis = np.linspace(0, 2*np.pi, n_angles)
        
        np.random.seed(true_key)
        full_idler = np.random.randint(0, 2, shots*n_angles, dtype=np.int8)
        
        s_bits = []
        cum_shots = [0]
        idx = 0
        
        for phi in phis:
            prob = (1 + np.cos(phi))/2
            rnd = np.random.rand(shots)
            seg_I = full_idler[idx:idx+shots]
            seg_S = np.zeros(shots)
            
            m0 = (seg_I == 0)
            seg_S[m0] = (rnd[m0] < prob).astype(np.int8)
            seg_S[~m0] = (rnd[~m0] < 0.5).astype(np.int8)
            
            s_bits.extend(seg_S)
            cum_shots.append(cum_shots[-1]+shots)
            idx += shots
            
        self.experiment_data = (np.array(s_bits), np.array(cum_shots), phis)
        self.true_key_len = bits
        self.log("Data intercepted. Starting solvers...")
        
        # Plot initial flat line
        self.ax.clear()
        self.ax.grid(color='#003300')
        self.ax.set_ylim(0, 1)
        self.ax.plot(phis, [0.5]*len(phis), color='green', linewidth=2)
        self.canvas.draw()
        
        self.is_scanning = True
        self._attack_logic()

    def _attack_logic(self):
        pool = ProcessPoolExecutor(max_workers=multiprocessing.cpu_count())
        
        found = False
        test_lens = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32]
        test_lens = [l for l in test_lens if l <= self.true_key_len + 4]
        
        for length in test_lens:
            if found: break
            self.current_scan_len = length
            
            self.after(0, lambda l=length: self.lbl_status.config(text=f"BRUTE-FORCING LAYER: {l}-bit"))
            self.log(f"Scanning bit-depth: {length}...")
            
            is_genetic = length > 14
            
            if not is_genetic:
                # BRUTE FORCE
                space = min(1<<length, 4000)
                candidates = [random.getrandbits(length) for _ in range(space)]
                
                tasks = [(c, length, self.experiment_data[0], self.experiment_data[1]) for c in candidates]
                results = list(pool.map(evaluate_candidate_vectorized, tasks))
                
                best = max(results, key=lambda x: x[0])
                self._handle_result(best, length)
                if best[0] > 0.85: found = True
                
            else:
                # GENETIC
                self.after(0, lambda: self.lbl_status.config(text=f"GENETIC EVOLUTION: {length}-bit"))
                pop_size = 1000
                population = [random.getrandbits(length) for _ in range(pop_size)]
                
                for gen in range(40):
                    tasks = [(c, length, self.experiment_data[0], self.experiment_data[1]) for c in population]
                    results = list(pool.map(evaluate_candidate_vectorized, tasks))
                    results.sort(key=lambda x: x[0], reverse=True)
                    
                    best = results[0]
                    self._handle_result(best, length)
                    
                    if best[0] > 0.85:
                        found = True
                        break
                        
                    # Evolve
                    new_pop = [r[1] for r in results[:100]]
                    while len(new_pop) < pop_size:
                        p1 = random.choice(results[:300])[1]
                        p2 = random.choice(results[:300])[1]
                        mask = random.getrandbits(length)
                        child = (p1 & mask) | (p2 & ~mask)
                        if random.random() < 0.3:
                            child ^= (1 << random.randint(0, length-1))
                        new_pop.append(child)
                    population = new_pop
        
        pool.shutdown()
        self.is_scanning = False
        
        if found:
            self.after(0, self._success_viz)
        else:
            self.log("HACK FAILED. Encryption too strong.")
            self.running = False
            self.after(0, lambda: self.btn_start.config(state="normal"))

    def _handle_result(self, best_tuple, length):
        score, key = best_tuple
        self.current_best_score = score
        self.current_best_key = key
        
        self.after(0, lambda: self.lbl_score.config(text=f"Visibility: {score*100:.2f}%"))
        self.after(0, lambda: self._update_main_graph(key, length))

    def _update_main_graph(self, key, length):
        s_bits, cum_shots, phis = self.experiment_data
        I_guess = generate_keystream_fast(key, len(s_bits))
        
        vals = []
        idx_start = 0
        for idx_end in cum_shots[1:]:
            seg_I = I_guess[idx_start:idx_end]
            seg_S = s_bits[idx_start:idx_end]
            m0 = (seg_I == 0)
            if np.sum(m0)>0: vals.append(np.mean(seg_S[m0]))
            else: vals.append(0.5)
            idx_start = idx_end
            
        # Redesenam complet doar cand avem o cheie mai buna
        self.ax.clear()
        self.ax.grid(color='#003300')
        self.ax.set_ylim(0, 1)
        self.ax.plot(phis, vals, 'o-', color='#00ff41', linewidth=2, label="Decrypted Signal")
        self.canvas.draw()

    def _success_viz(self):
        self.lbl_status.config(text="ACCESS GRANTED", foreground="white", background="#00aa00")
        self.log("KEY FOUND.")
        self.log(f"BINARY: {bin(self.current_best_key)}")
        
        self.matrix_text.insert("1.0", f"\n>>> MATCH FOUND: {bin(self.current_best_key)} <<<\n", "hit")
        self.matrix_text.see("1.0")
        messagebox.showinfo("HACKED", f"Sistem compromis!\nCheia: {bin(self.current_best_key)}")
        self.running = False
        self.btn_start.config(state="normal")

if __name__ == "__main__":
    multiprocessing.freeze_support()
    app = CyberpunkApp()
    app.mainloop()
