#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from collections import Counter
from itertools import groupby
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import csv
import re
from datetime import datetime
from collections import defaultdict
try:
    from wordfreq import zipf_frequency
    WORDFREQ_AVAILABLE = True
except Exception:
    WORDFREQ_AVAILABLE = False


class BinaryAnalyzerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Analizor Date Binare Avansat")
        self.root.geometry("900x700")
        
        # Variabile
        self.input_file = tk.StringVar(value="datebinare.txt")
        self.binary_data = ""
        self.analysis_results = {}
        self.sliding_mode_var = tk.BooleanVar(value=False)
        self.letter_results = {
            'letters': [],
            'positions': [],
            'freq': Counter(),
            'total_chunks_considered': 0,
            'sliding_mode': False
        }
        # Variabile pentru dicționar/cuvinte
        # Implicit, dacă există dictionary_en.txt în directorul curent, îl selectăm
        default_dict = "dictionary_en.txt"
        self.dictionary_file = tk.StringVar(value=default_dict if os.path.exists(default_dict) else "")
        self.min_word_len_var = tk.IntVar(value=3)
        self.zipf_threshold_var = tk.DoubleVar(value=3.0)
        
        self.setup_ui()
        
    def setup_ui(self):
        # Frame principal
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # Configurare grid
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # Secțiunea fișier
        ttk.Label(main_frame, text="Fișier de intrare:").grid(row=0, column=0, sticky=tk.W, pady=5)
        ttk.Entry(main_frame, textvariable=self.input_file, width=50).grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
        ttk.Button(main_frame, text="Browse", command=self.browse_file).grid(row=0, column=2, padx=5, pady=5)
        
        # Buton încărcare
        ttk.Button(main_frame, text="Încarcă Date", command=self.load_data).grid(row=1, column=0, columnspan=3, pady=10)
        
        # Separator
        ttk.Separator(main_frame, orient='horizontal').grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
        
        # Secțiunea analiză alternanțe
        ttk.Label(main_frame, text="Analiză Alternanțe Personalizate", font=('Arial', 12, 'bold')).grid(row=3, column=0, columnspan=3, pady=10)
        
        ttk.Label(main_frame, text="Pattern-uri de alternanță (separate prin spațiu):").grid(row=4, column=0, sticky=tk.W, pady=5)
        self.alternance_patterns = tk.Text(main_frame, height=3, width=50)
        self.alternance_patterns.grid(row=4, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        self.alternance_patterns.insert(tk.END, "001 11110 010 3")
        
        ttk.Button(main_frame, text="Analizează Alternanțe", command=self.analyze_alternances).grid(row=5, column=0, columnspan=3, pady=10)
        
        # Separator
        ttk.Separator(main_frame, orient='horizontal').grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
        
        # Butoane analiză standard
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=7, column=0, columnspan=3, pady=10)
        
        ttk.Button(button_frame, text="Analiză Standard", command=self.run_standard_analysis).grid(row=0, column=0, padx=5)
        ttk.Button(button_frame, text="Analiză Completă", command=self.run_complete_analysis).grid(row=0, column=1, padx=5)
        ttk.Button(button_frame, text="Generează Grafice Suplimentare", command=self.generate_extra_plots).grid(row=0, column=2, padx=5)
        ttk.Button(button_frame, text="Generează Raport", command=self.generate_report).grid(row=0, column=3, padx=5)

        # Separator
        ttk.Separator(main_frame, orient='horizontal').grid(row=8, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)

        # Secțiunea căutare litere ASCII
        ttk.Label(main_frame, text="Căutare litere ASCII (8 biți)", font=('Arial', 12, 'bold')).grid(row=9, column=0, columnspan=3, pady=10)
        ttk.Checkbutton(main_frame, text="Mod glisant (verifică fiecare poziție)", variable=self.sliding_mode_var).grid(row=10, column=0, columnspan=2, sticky=tk.W)
        ttk.Button(main_frame, text="Caută litere ASCII", command=self.search_ascii_letters).grid(row=10, column=2, sticky=tk.E)

        # Secțiunea dicționar/cuvinte englezești
        ttk.Label(main_frame, text="Detectare cuvinte englezești (aliniat pe octeți)", font=('Arial', 12, 'bold')).grid(row=11, column=0, columnspan=3, pady=10)
        ttk.Label(main_frame, text="Fișier dicționar (opțional, .txt cu un cuvânt pe linie):").grid(row=12, column=0, sticky=tk.W)
        ttk.Entry(main_frame, textvariable=self.dictionary_file, width=50).grid(row=12, column=1, sticky=(tk.W, tk.E))
        ttk.Button(main_frame, text="Browse", command=self.browse_dictionary_file).grid(row=12, column=2, sticky=tk.E)
        ttk.Label(main_frame, text="Lungime minimă cuvânt:").grid(row=13, column=0, sticky=tk.W)
        ttk.Entry(main_frame, textvariable=self.min_word_len_var, width=8).grid(row=13, column=1, sticky=tk.W)
        ttk.Label(main_frame, text="Prag frecvență Zipf (wordfreq, dacă e disponibil):").grid(row=14, column=0, sticky=tk.W)
        ttk.Entry(main_frame, textvariable=self.zipf_threshold_var, width=8).grid(row=14, column=1, sticky=tk.W)
        tz = "Disponibil" if WORDFREQ_AVAILABLE else "Indisponibil (instalați 'wordfreq')"
        ttk.Label(main_frame, text=f"wordfreq: {tz}").grid(row=14, column=2, sticky=tk.E)
        ttk.Button(main_frame, text="Caută cuvinte englezești", command=self.find_english_words).grid(row=15, column=2, sticky=tk.E, pady=5)
        
        # Zona de rezultate
        results_frame = ttk.Frame(main_frame)
        results_frame.grid(row=16, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
        main_frame.rowconfigure(16, weight=1)
        
        # Scrollbar pentru rezultate
        scrollbar = ttk.Scrollbar(results_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        self.results_text = tk.Text(results_frame, height=15, width=80, yscrollcommand=scrollbar.set)
        self.results_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=self.results_text.yview)
        
        # Bară de stare
        self.status_bar = ttk.Label(main_frame, text="Gata", relief=tk.SUNKEN)
        self.status_bar.grid(row=17, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
        
    def browse_file(self):
        filename = filedialog.askopenfilename(
            title="Selectează fișierul cu date binare",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if filename:
            self.input_file.set(filename)
            
    def update_status(self, message):
        self.status_bar.config(text=message)
        self.root.update()
        
    def log_result(self, message):
        self.results_text.insert(tk.END, message + "\n")
        self.results_text.see(tk.END)
        self.root.update()
        
    def read_bits(self, path):
        try:
            with open(path, "r", encoding="utf-8") as f:
                data = f.read().strip()
            for ch in set(data):
                if ch not in {"0", "1"}:
                    raise ValueError(f"Fișierul conține caracter invalid: {repr(ch)}")
            if not data:
                raise ValueError("Fișierul este gol.")
            return data
        except Exception as e:
            messagebox.showerror("Eroare", f"Eroare la citirea fișierului: {str(e)}")
            return None
            
    def load_data(self):
        self.update_status("Se încarcă datele...")
        file_path = self.input_file.get()
        if not os.path.exists(file_path):
            messagebox.showerror("Eroare", f"Fișierul {file_path} nu există.")
            return
            
        self.binary_data = self.read_bits(file_path)
        if self.binary_data:
            self.log_result(f"Date încărcate cu succes: {len(self.binary_data):,} biți")
            self.log_result(f"Distribuție: {self.binary_data.count('0')} zerouri, {self.binary_data.count('1')} uniți")
            self.update_status("Date încărcate")
        else:
            self.update_status("Eroare la încărcare")
            
    def run_length_encode(self, bits):
        return [(bit, sum(1 for _ in grp)) for bit, grp in groupby(bits)]
        
    def count_pattern_alternances(self, data, patterns):
        """Contorizează aparițiile pattern-urilor de alternanță specificate"""
        results = {}
        
        for pattern in patterns:
            pattern = pattern.strip()
            if not pattern:
                continue
                
            # Caută toate aparițiile pattern-ului
            count = len(re.findall(f'(?={pattern})', data))
            percentage = (count / len(data)) * 100 if data else 0
            
            results[pattern] = {
                'count': count,
                'percentage': percentage,
                'pattern_length': len(pattern)
            }
            
        return results

    def ensure_outdir(self):
        outdir = "rezultate"
        os.makedirs(outdir, exist_ok=True)
        return outdir

    def generate_extra_plots(self):
        """Generează grafice suplimentare pentru rezultatele curente (alternanțe și run-length)."""
        if not self.analysis_results:
            messagebox.showwarning("Atenție", "Nu există rezultate de analiză pentru a genera grafice.")
            return
        outdir = self.ensure_outdir()
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

        # Grafice suplimentare pentru alternanțe: pie chart și bar chart
        if 'alternances' in self.analysis_results and self.analysis_results['alternances']:
            alt = self.analysis_results['alternances']
            labels = list(alt.keys())
            sizes = [alt[p]['percentage'] for p in labels]
            counts = [alt[p]['count'] for p in labels]

            if any(sizes):
                plt.figure(figsize=(6,6))
                plt.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
                plt.title('Procentaj pattern-uri alternanță')
                plt.tight_layout()
                pie_path = os.path.join(outdir, f"alternante_pie_{timestamp}.png")
                plt.savefig(pie_path, dpi=150)
                plt.close()
                self.log_result(f"Grafic salvat: {pie_path}")

            plt.figure(figsize=(8,5))
            plt.bar(labels, counts)
            plt.title('Apariții pattern-uri alternanță')
            plt.xlabel('Pattern')
            plt.ylabel('Count')
            plt.tight_layout()
            bar_path = os.path.join(outdir, f"alternante_bar_{timestamp}.png")
            plt.savefig(bar_path, dpi=150)
            plt.close()
            self.log_result(f"Grafic salvat: {bar_path}")

        # Grafice suplimentare pentru run-length: histogramă combinată și comparație pe aceeași figură
        if 'standard' in self.analysis_results:
            std = self.analysis_results['standard']
            c0 = std['distribution_0']
            c1 = std['distribution_1']

            # Histogramă combinată (0 și 1 la un loc)
            combined = defaultdict(int)
            for k, v in c0.items():
                combined[k] += v
            for k, v in c1.items():
                combined[k] += v

            xs = sorted(combined.keys())
            total_combined = sum(combined.values())
            ys = [(combined[x] / total_combined * 100) if total_combined else 0 for x in xs]

            plt.figure(figsize=(8,5))
            plt.bar([str(x) for x in xs], ys)
            plt.title('Distribuție combinată a lungimilor run-urilor (0 și 1)')
            plt.xlabel('Lungime run')
            plt.ylabel('Procent (%)')
            plt.tight_layout()
            comb_path = os.path.join(outdir, f"run_distribution_combined_{timestamp}.png")
            plt.savefig(comb_path, dpi=150)
            plt.close()
            self.log_result(f"Grafic salvat: {comb_path}")

            # Comparație pe aceeași figură (0 vs 1)
            xs_cmp = sorted(set(list(c0.keys()) + list(c1.keys())))
            total_0 = sum(c0.values())
            total_1 = sum(c1.values())
            y0 = [(c0[x] / total_0 * 100) if total_0 else 0 for x in xs_cmp]
            y1 = [(c1[x] / total_1 * 100) if total_1 else 0 for x in xs_cmp]

            plt.figure(figsize=(8,5))
            plt.plot(xs_cmp, y0, marker='o', label='0')
            plt.plot(xs_cmp, y1, marker='s', label='1')
            plt.title('Comparație distribuții run-length (0 vs 1)')
            plt.xlabel('Lungime run')
            plt.ylabel('Procent (%)')
            plt.legend()
            plt.tight_layout()
            cmp_path = os.path.join(outdir, f"run_distribution_compare_{timestamp}.png")
            plt.savefig(cmp_path, dpi=150)
            plt.close()
            self.log_result(f"Grafic salvat: {cmp_path}")

    def browse_dictionary_file(self):
        filename = filedialog.askopenfilename(
            title="Selectează fișierul dicționar",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if filename:
            self.dictionary_file.set(filename)

    def load_dictionary_words(self, path):
        words = set()
        try:
            with open(path, 'r', encoding='utf-8') as f:
                for line in f:
                    w = line.strip().lower()
                    if w and w.isalpha():
                        words.add(w)
        except Exception as e:
            messagebox.showwarning("Atenție", f"Nu s-a putut încărca dicționarul: {e}")
        return words

    def decode_bytes_aligned(self):
        """Decodează biții în octeți aliniați și întoarce lista de tuple (idx_octet, caracter)."""
        chars = []
        nbytes = len(self.binary_data) // 8
        for idx in range(nbytes):
            i = idx * 8
            byte_str = self.binary_data[i:i+8]
            val = int(byte_str, 2)
            ch = chr(val)
            chars.append((idx, ch))
        return chars

    def find_english_words(self):
        if not self.binary_data:
            messagebox.showwarning("Atenție", "Nu există date încărcate.")
            return
        self.update_status("Se caută cuvinte englezești…")
        outdir = self.ensure_outdir()
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

        dict_path = self.dictionary_file.get().strip()
        dict_words = set()
        if dict_path:
            dict_words = self.load_dictionary_words(dict_path)
            self.log_result(f"Dicționar încărcat: {len(dict_words):,} cuvinte")

        min_len = max(1, int(self.min_word_len_var.get()))
        zipf_thr = float(self.zipf_threshold_var.get())

        chars = self.decode_bytes_aligned()
        total_units = len(chars)  # total octeți analizați

        words_found = []
        word_positions = []  # poziția de start a cuvântului (octet)

        # Construiește cuvinte din secvențe de litere A-Za-z
        current = []
        current_start = None
        for idx, ch in chars:
            if re.match(r"[A-Za-z]", ch):
                if current_start is None:
                    current_start = idx
                current.append(ch)
            else:
                if current:
                    w = ''.join(current)
                    words_found.append(w)
                    word_positions.append(current_start)
                    current = []
                    current_start = None
        if current:
            w = ''.join(current)
            words_found.append(w)
            word_positions.append(current_start)

        # Filtrare după dicționar / wordfreq / lungime minimă
        valid_words = []
        valid_positions = []
        for w, pos in zip(words_found, word_positions):
            wl = w.lower()
            is_valid = False
            if dict_words:
                is_valid = wl in dict_words and len(wl) >= min_len
            elif WORDFREQ_AVAILABLE:
                is_valid = (len(wl) >= min_len) and (zipf_frequency(wl, 'en') >= zipf_thr)
            else:
                # Fallback: doar lungimea minimă
                is_valid = len(wl) >= min_len
            if is_valid:
                valid_words.append(w)
                valid_positions.append(pos)

        # Log detaliat: fiecare cuvânt găsit în ordine
        self.log_result("")
        self.log_result("="*50)
        self.log_result("CĂUTARE CUVINTE ENGLEZEȘTI")
        self.log_result("="*50)
        self.log_result(f"Total octeți analizați: {total_units:,}")
        self.log_result(f"Cuvinte candidate extrase: {len(words_found):,}")
        self.log_result(f"Cuvinte validate: {len(valid_words):,}")
        for w, pos in zip(valid_words[:1000], valid_positions[:1000]):
            self.log_result(f"Cuvânt: '{w}' la poziția octet {pos}")
        if len(valid_words) > 1000:
            self.log_result(f"… și alte {len(valid_words)-1000:,} cuvinte")

        # Statistici și grafice
        freq = Counter([w.lower() for w in valid_words])
        total_valid = len(valid_words)
        pct_valid = (total_valid / (len(words_found) if words_found else 1) * 100)
        self.analysis_results['english_words'] = {
            'words': valid_words,
            'positions': valid_positions,
            'freq': freq,
            'total_units': total_units,
            'total_candidates': len(words_found),
            'percentage_valid_of_candidates': pct_valid,
            'dict_size': len(dict_words),
            'min_len': min_len,
            'zipf_thr': zipf_thr,
            'wordfreq_available': WORDFREQ_AVAILABLE,
        }

        # Bar chart top 20 cuvinte
        if freq:
            top = freq.most_common(20)
            labels = [t[0] for t in top]
            counts = [t[1] for t in top]
            plt.figure(figsize=(10,5))
            plt.bar(labels, counts)
            plt.title('Top 20 cuvinte englezești găsite')
            plt.xlabel('Cuvânt')
            plt.ylabel('Count')
            plt.xticks(rotation=45, ha='right')
            plt.tight_layout()
            top_path = os.path.join(outdir, f"english_words_top20_{timestamp}.png")
            plt.savefig(top_path, dpi=150)
            plt.close()
            self.log_result(f"Grafic salvat: {top_path}")

        # Histogramă lungimi cuvinte
        if valid_words:
            lengths = [len(w) for w in valid_words]
            plt.figure(figsize=(8,5))
            plt.hist(lengths, bins=range(1, max(lengths)+2), align='left', rwidth=0.8)
            plt.title('Histogramă lungimi cuvinte englezești')
            plt.xlabel('Lungime cuvânt')
            plt.ylabel('Count')
            plt.tight_layout()
            len_path = os.path.join(outdir, f"english_words_lengths_{timestamp}.png")
            plt.savefig(len_path, dpi=150)
            plt.close()
            self.log_result(f"Grafic salvat: {len_path}")

        # Salvează cuvintele validate într-un fișier text pe o singură linie
        try:
            words_line_path = os.path.join(outdir, f"english_words_line_{timestamp}.txt")
            with open(words_line_path, 'w', encoding='utf-8') as wf:
                wf.write(' '.join(valid_words))
            self.log_result(f"Fișier cuvinte (o singură linie) salvat: {words_line_path}")
        except Exception as e:
            self.log_result(f"Atenție: nu s-au putut salva cuvintele într-un fișier linie: {e}")

        self.update_status("Căutare cuvinte finalizată")
        
    def analyze_alternances(self):
        if not self.binary_data:
            messagebox.showwarning("Atenție", "Nu există date încărcate.")
            return
            
        self.update_status("Se analizează alternanțele...")
        
        # Obține pattern-urile din text widget
        patterns_text = self.alternance_patterns.get("1.0", tk.END).strip()
        patterns = patterns_text.split()
        
        if not patterns:
            messagebox.showwarning("Atenție", "Nu ați specificat niciun pattern de alternanță.")
            return
            
        self.log_result("\n" + "="*50)
        self.log_result("ANALIZĂ ALTERNANȚE PERSONALIZATE")
        self.log_result("="*50)
        
        results = self.count_pattern_alternances(self.binary_data, patterns)
        
        for pattern, data in results.items():
            self.log_result(f"Pattern '{pattern}' (lungime {data['pattern_length']})")
            self.log_result(f"  Apariții: {data['count']:,}")
            self.log_result(f"  Procentaj: {data['percentage']:.3f}%")
            self.log_result("")
            
        # Salvează rezultatele pentru raport
        self.analysis_results['alternances'] = results
        # Generează grafice suplimentare pentru alternanțe imediat
        self.generate_extra_plots()

        self.update_status("Analiză alternanțe completă")
        
    def run_standard_analysis(self):
        if not self.binary_data:
            messagebox.showwarning("Atenție", "Nu există date încărcate.")
            return
            
        self.update_status("Se rulează analiza standard...")
        
        runs = self.run_length_encode(self.binary_data)
        
        # Contorizare pe lungimi
        c0, c1 = Counter(), Counter()
        for bit, length in runs:
            if bit == "0":
                c0[length] += 1
            else:
                c1[length] += 1
                
        max0 = max((L for b, L in runs if b == "0"), default=0)
        max1 = max((L for b, L in runs if b == "1"), default=0)
        
        self.log_result("\n" + "="*50)
        self.log_result("ANALIZĂ STANDARD")
        self.log_result("="*50)
        self.log_result(f"Total biți: {len(self.binary_data):,}")
        self.log_result(f"Număr total run-uri: {len(runs):,}")
        self.log_result(f"Cea mai lungă serie de 0: {max0}")
        self.log_result(f"Cea mai lungă serie de 1: {max1}")
        self.log_result("")
        
        # Distribuție procentuală
        self.log_result("Distribuția lungimilor pentru 0:")
        total_0 = sum(c0.values())
        for length in sorted(c0.keys()):
            pct = (c0[length] / total_0 * 100) if total_0 > 0 else 0
            self.log_result(f"  Lungime {length}: {c0[length]:,} apariții ({pct:.3f}%)")
            
        self.log_result("")
        self.log_result("Distribuția lungimilor pentru 1:")
        total_1 = sum(c1.values())
        for length in sorted(c1.keys()):
            pct = (c1[length] / total_1 * 100) if total_1 > 0 else 0
            self.log_result(f"  Lungime {length}: {c1[length]:,} apariții ({pct:.3f}%)")
            
        # Salvează rezultatele pentru raport
        self.analysis_results['standard'] = {
            'total_bits': len(self.binary_data),
            'total_runs': len(runs),
            'max_0_run': max0,
            'max_1_run': max1,
            'distribution_0': c0,
            'distribution_1': c1
        }
        # Grafice suplimentare pentru run-length
        self.generate_extra_plots()

        self.update_status("Analiză standard completă")

    def search_ascii_letters(self):
        """Caută orice 8 biți care formează o literă ASCII (A-Z, a-z).
        Afișează literele găsite consecutiv în ordinea descoperirii.
        Mod aliniere:
          - aliniat: parcurge biții în pași de 8
          - glisant: verifică fiecare poziție (fereastră 8 biți)
        """
        if not self.binary_data:
            messagebox.showwarning("Atenție", "Nu există date încărcate.")
            return
        self.update_status("Se caută litere ASCII…")
        outdir = self.ensure_outdir()
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

        sliding = bool(self.sliding_mode_var.get())
        letters = []
        positions = []

        data = self.binary_data
        n = len(data)
        if not sliding:
            # Aliniat: pași de 8
            total_chunks = n // 8
            for idx in range(total_chunks):
                i = idx * 8
                byte_str = data[i:i+8]
                val = int(byte_str, 2)
                if (65 <= val <= 90) or (97 <= val <= 122):
                    ch = chr(val)
                    letters.append(ch)
                    positions.append(idx)
                    self.log_result(f"Literă găsită: '{ch}' (ASCII {val}) la poziția octet {idx}")
        else:
            # Glisant: fiecare poziție
            total_chunks = max(0, n - 7)
            for i in range(0, n - 7):
                byte_str = data[i:i+8]
                val = int(byte_str, 2)
                if (65 <= val <= 90) or (97 <= val <= 122):
                    ch = chr(val)
                    letters.append(ch)
                    positions.append(i)
                    self.log_result(f"Literă găsită: '{ch}' (ASCII {val}) la poziția bit {i}")

        freq = Counter(letters)
        self.letter_results = {
            'letters': letters,
            'positions': positions,
            'freq': freq,
            'total_chunks_considered': total_chunks,
            'sliding_mode': sliding
        }
        # Salvează în analysis_results pentru raport
        self.analysis_results['letters_ascii'] = self.letter_results

        # Rezumat în log
        total_letters = len(letters)
        pct_letters = (total_letters / total_chunks * 100) if total_chunks else 0
        self.log_result("")
        self.log_result("="*50)
        self.log_result("CĂUTARE LITERE ASCII")
        self.log_result("="*50)
        self.log_result(f"Mod: {'glisant' if sliding else 'aliniat'}")
        self.log_result(f"Total octeți verificați: {total_chunks:,}")
        self.log_result(f"Total litere găsite: {total_letters:,} ({pct_letters:.3f}%)")
        if freq:
            self.log_result("Frecvențe litere:")
            for ch in sorted(freq.keys()):
                cnt = freq[ch]
                pct = (cnt / total_letters * 100) if total_letters else 0
                self.log_result(f"  {ch}: {cnt:,} ({pct:.3f}%)")

        # Grafice pentru litere: bar chart frecvențe și poziții
        if freq:
            labels = list(sorted(freq.keys()))
            counts = [freq[ch] for ch in labels]

            plt.figure(figsize=(8,5))
            plt.bar(labels, counts)
            plt.title('Frecvențe litere ASCII găsite')
            plt.xlabel('Literă')
            plt.ylabel('Count')
            plt.tight_layout()
            freq_path = os.path.join(outdir, f"ascii_letters_freq_{timestamp}.png")
            plt.savefig(freq_path, dpi=150)
            plt.close()
            self.log_result(f"Grafic salvat: {freq_path}")

        if positions:
            plt.figure(figsize=(8,4))
            plt.plot(range(len(positions)), positions, marker='.', linestyle='none')
            plt.title('Poziții în flux pentru litere găsite')
            plt.xlabel('Index literă găsită (ordine de descoperire)')
            plt.ylabel('Poziție (octet)' if not sliding else 'Poziție (bit)')
            plt.tight_layout()
            pos_path = os.path.join(outdir, f"ascii_letters_positions_{timestamp}.png")
            plt.savefig(pos_path, dpi=150)
            plt.close()
            self.log_result(f"Grafic salvat: {pos_path}")

        self.update_status("Căutare litere finalizată")
        
    def run_complete_analysis(self):
        self.run_standard_analysis()
        self.analyze_alternances()
        
    def generate_report(self):
        if not self.analysis_results:
            messagebox.showwarning("Atenție", "Nu există rezultate de analiză.")
            return
            
        self.update_status("Se generează raportul...")
        
        # Creează directorul de rezultate dacă nu există
        outdir = self.ensure_outdir()
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        report_file = os.path.join(outdir, f"raport_analiza_{timestamp}.txt")
        
        with open(report_file, 'w', encoding='utf-8') as f:
            f.write("RAPORT ANALIZĂ DATE BINARE\n")
            f.write("="*50 + "\n")
            f.write(f"Data și ora: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"Fișier de intrare: {self.input_file.get()}\n\n")
            
            if 'standard' in self.analysis_results:
                std = self.analysis_results['standard']
                f.write("ANALIZĂ STANDARD\n")
                f.write("-"*30 + "\n")
                f.write(f"Total biți: {std['total_bits']:,}\n")
                f.write(f"Număr total run-uri: {std['total_runs']:,}\n")
                f.write(f"Cea mai lungă serie de 0: {std['max_0_run']}\n")
                f.write(f"Cea mai lungă serie de 1: {std['max_1_run']}\n\n")
                
                # Distribuții
                f.write("Distribuția lungimilor pentru 0:\n")
                total_0 = sum(std['distribution_0'].values())
                for length in sorted(std['distribution_0'].keys()):
                    count = std['distribution_0'][length]
                    pct = (count / total_0 * 100) if total_0 > 0 else 0
                    f.write(f"  Lungime {length}: {count:,} apariții ({pct:.3f}%)\n")
                    
                f.write("\nDistribuția lungimilor pentru 1:\n")
                total_1 = sum(std['distribution_1'].values())
                for length in sorted(std['distribution_1'].keys()):
                    count = std['distribution_1'][length]
                    pct = (count / total_1 * 100) if total_1 > 0 else 0
                    f.write(f"  Lungime {length}: {count:,} apariții ({pct:.3f}%)\n")
                    
            if 'alternances' in self.analysis_results:
                f.write("\n" + "="*50 + "\n")
                f.write("ANALIZĂ ALTERNANȚE PERSONALIZATE\n")
                f.write("-"*40 + "\n")
                
                for pattern, data in self.analysis_results['alternances'].items():
                    f.write(f"Pattern '{pattern}' (lungime {data['pattern_length']})\n")
                    f.write(f"  Apariții: {data['count']:,}\n")
                    f.write(f"  Procentaj: {data['percentage']:.3f}%\n\n")

            if 'letters_ascii' in self.analysis_results:
                letters_info = self.analysis_results['letters_ascii']
                f.write("\n" + "="*50 + "\n")
                f.write("CĂUTARE LITERE ASCII (8 biți)\n")
                f.write("-"*40 + "\n")
                f.write(f"Mod: {'glisant' if letters_info['sliding_mode'] else 'aliniat'}\n")
                f.write(f"Total unități verificate: {letters_info['total_chunks_considered']:,}\n")
                total_letters = len(letters_info['letters'])
                pct_letters = (total_letters / letters_info['total_chunks_considered'] * 100) if letters_info['total_chunks_considered'] else 0
                f.write(f"Total litere găsite: {total_letters:,} ({pct_letters:.3f}%)\n")
                if letters_info['freq']:
                    f.write("Frecvențe litere:\n")
                    for ch in sorted(letters_info['freq'].keys()):
                        cnt = letters_info['freq'][ch]
                        p = (cnt / total_letters * 100) if total_letters else 0
                        f.write(f"  {ch}: {cnt:,} ({p:.3f}%)\n")

            if 'english_words' in self.analysis_results:
                ew = self.analysis_results['english_words']
                f.write("\n" + "="*50 + "\n")
                f.write("CĂUTARE CUVINTE ENGLEZEȘTI\n")
                f.write("-"*40 + "\n")
                f.write(f"Total octeți analizați: {ew['total_units']:,}\n")
                f.write(f"Cuvinte candidate extrase: {ew['total_candidates']:,}\n")
                f.write(f"Cuvinte validate: {len(ew['words']):,} ({ew['percentage_valid_of_candidates']:.3f}% din candidați)\n")
                f.write(f"Dicționar: {ew['dict_size']:,} cuvinte\n")
                f.write(f"Prag lungime minimă: {ew['min_len']}\n")
                f.write(f"Prag Zipf: {ew['zipf_thr']:.2f} (wordfreq: {'da' if ew['wordfreq_available'] else 'nu'})\n")
                # Top 20 în raport
                if ew['freq']:
                    top = ew['freq'].most_common(20)
                    f.write("Top 20 cuvinte:\n")
                    for w, cnt in top:
                        p = (cnt / len(ew['words']) * 100) if ew['words'] else 0
                        f.write(f"  {w}: {cnt:,} ({p:.3f}%)\n")
            if 'letters_ascii' in self.analysis_results:
                letters_info = self.analysis_results['letters_ascii']
                f.write("\n" + "="*50 + "\n")
                f.write("CĂUTARE LITERE ASCII (8 biți)\n")
                f.write("-"*40 + "\n")
                f.write(f"Mod: {'glisant' if letters_info['sliding_mode'] else 'aliniat'}\n")
                f.write(f"Total unități verificate: {letters_info['total_chunks_considered']:,}\n")
                total_letters = len(letters_info['letters'])
                pct_letters = (total_letters / letters_info['total_chunks_considered'] * 100) if letters_info['total_chunks_considered'] else 0
                f.write(f"Total litere găsite: {total_letters:,} ({pct_letters:.3f}%)\n")
                if letters_info['freq']:
                    f.write("Frecvențe litere:\n")
                    for ch in sorted(letters_info['freq'].keys()):
                        cnt = letters_info['freq'][ch]
                        p = (cnt / total_letters * 100) if total_letters else 0
                        f.write(f"  {ch}: {cnt:,} ({p:.3f}%)\n")
                    
        self.log_result(f"\nRaport salvat: {report_file}")
        self.update_status("Raport generat")
        
        # Deschide fișierul
        try:
            os.startfile(report_file)
        except:
            pass


def main():
    root = tk.Tk()
    app = BinaryAnalyzerGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()