import cv2
import numpy as np
import os
import json
import time
import threading
import queue
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Optional, Tuple
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from PIL import Image, ImageTk
import customtkinter as ctk

# Konfiguracja CustomTkinter - motyw zielony/ciemny jak na stronie
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")

# ==================== KONFIGURACJA ====================

CONFIG_DIR = Path("ascii_config")
CONFIG_FILE = CONFIG_DIR / "settings.json"
RECORDINGS_DIR = Path("ascii_recordings")

CONFIG_DIR.mkdir(exist_ok=True)
RECORDINGS_DIR.mkdir(exist_ok=True)

# Zestawy znaków ASCII
ASCII_SETS = {
    "standard": "@%#*+=-:. ",
    "detailed": "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. ",
    "simple": "#=-. ",
    "block": "▓▒░ ",
    "minimal": "██▉▊▋▌▅▴▃▂▁ ",
    "circuit": "█▓▒░░ ",
    "inverse": " .:-=+*#%@█",
    "heavy": "█▓▒░",
    "gradients": "░▒▓█",
}

# Predefiniowane profile
PROFILES = {
    "normal": {
        "width": 120,
        "char_set": "standard",
        "use_color": False,
        "outline_only": False,
        "invert_colors": False,
        "brightness": 1.0,
        "contrast": 1.0,
        "sharpen": False,
        "target_fps": 30,
    },
    "detailed": {
        "width": 150,
        "char_set": "detailed",
        "use_color": True,
        "outline_only": False,
        "invert_colors": False,
        "brightness": 1.1,
        "contrast": 1.2,
        "sharpen": True,
        "target_fps": 20,
    },
    "performance": {
        "width": 80,
        "char_set": "simple",
        "use_color": False,
        "outline_only": False,
        "invert_colors": False,
        "brightness": 1.0,
        "contrast": 1.0,
        "sharpen": False,
        "target_fps": 60,
    },
    "artistic": {
        "width": 100,
        "char_set": "block",
        "use_color": False,
        "outline_only": True,
        "invert_colors": False,
        "brightness": 0.9,
        "contrast": 1.3,
        "sharpen": False,
        "target_fps": 30,
    },
}

# ==================== FUNKCJE POMOCNICZE ====================

def save_config(config: Dict[str, Any]) -> bool:
    """Zapisz konfigurację do pliku JSON"""
    try:
        with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
            json.dump(config, f, indent=4, ensure_ascii=False)
        return True
    except Exception as e:
        print(f"Błąd przy zapisywaniu: {e}")
        return False

def load_config() -> Dict[str, Any]:
    """Załaduj konfigurację z pliku JSON"""
    try:
        if CONFIG_FILE.exists():
            with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
    except Exception as e:
        print(f"Nie można załadować konfigu: {e}")
    return {}

def get_timestamp() -> str:
    """Zwróć timestamp dla nazwy pliku"""
    return datetime.now().strftime("%Y%m%d_%H%M%S")

def frame_to_ascii(frame, width: int, use_color=False, outline_only=False, 
                   invert_colors=False, brightness=1.0, contrast=1.0, 
                   sharpen=False, char_set="standard") -> str:
    """Konwertuj klatkę wideo na ASCII art"""
    try:
        # Odwrócenie poziome (usuwa efekt lustra)
        frame = cv2.flip(frame, 1)

        # Dostosowanie jasności i kontrastu
        frame = cv2.convertScaleAbs(frame, alpha=contrast, beta=(brightness - 1.0) * 100)

        # Wyostrzanie (opcjonalne)
        if sharpen:
            kernel = np.array([[0, -1, 0],
                               [-1, 5, -1],
                               [0, -1, 0]])
            frame = cv2.filter2D(frame, -1, kernel)

        # Obliczenie nowej wysokości z zachowaniem proporcji
        h, w = frame.shape[:2]
        ratio = h / w / 0.55
        new_height = max(1, int(width * ratio))
        resized = cv2.resize(frame, (width, new_height), interpolation=cv2.INTER_AREA)

        # Konwersja do szarości
        gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)

        # Odwrócenie kolorów (opcjonalne)
        if invert_colors:
            gray = 255 - gray

        chars = ASCII_SETS.get(char_set, ASCII_SETS["standard"])
        ascii_image = []

        for i in range(gray.shape[0]):
            line = []
            for j in range(gray.shape[1]):
                pixel = int(gray[i, j])
                char = chars[pixel * len(chars) // 256]

                if outline_only:
                    char = "#" if pixel < 128 else " "

                if use_color:
                    b, g, r = resized[i, j]
                    line.append(f"\033[38;2;{int(r)};{int(g)};{int(b)}m{char}")
                else:
                    line.append(char)
            ascii_image.append("".join(line))

        return "\n".join(ascii_image)
    except Exception as e:
        return f"Error: {e}"

# ==================== GŁÓWNE GUI ====================

class ASCIICameraGUI:
    """Główne okno aplikacji ASCII Camera v2.0"""
    
    def __init__(self):
        self.root = ctk.CTk()
        self.root.title("ASCII CAMERA v2.0 - Nowoczesny Interfejs")
        
        # Pełnoekranowy tryb z możliwością przełączenia
        self.fullscreen = True
        self.root.state('zoomed')  # Windows fullscreen
        
        # Minimalny rozmiar
        self.root.minsize(1200, 800)
        
        # Kolory motywu z strony internetowej
        self.colors = {
            'primary': '#00ff00',
            'secondary': '#00cc00', 
            'dark_bg': '#0a0e27',
            'darker_bg': '#050810',
            'text_color': '#e0e0e0',
            'accent': '#bb00bb',
            'border': '#1a3a3a'
        }
        
        # Ustawienia
        self.load_settings()
        
        # Konfiguracja CustomTkinter z motywu strony
        self.configure_custom_theme()
        
        # Zmienne stanu
        self.camera_running = False
        self.camera_thread = None
        self.frame_queue = queue.Queue(maxsize=2)
        self.current_frame = None
        self.ascii_text = ""
        self.fps_counter = 0
        self.fps_start_time = time.time()
        self.current_fps = 0
        
        # Bind klawiszy
        self.root.bind('<F11>', self.toggle_fullscreen)
        self.root.bind('<Escape>', lambda e: self.exit_fullscreen())
        
        # Tworzenie GUI
        self.setup_ui()
        
        # Start odświeżania
        self.update_display()
        
    def load_settings(self):
        """Załaduj ustawienia"""
        saved = load_config()
        self.width = saved.get('width', 120)
        self.use_color = saved.get('use_color', False)
        self.outline_only = saved.get('outline_only', False)
        self.invert_colors = saved.get('invert_colors', False)
        self.brightness = saved.get('brightness', 1.0)
        self.contrast = saved.get('contrast', 1.0)
        self.sharpen = saved.get('sharpen', False)
        self.target_fps = saved.get('target_fps', 30)
        self.show_fps = saved.get('show_fps', True)
        self.camera_index = saved.get('camera_index', 0)
        self.char_set = saved.get('char_set', 'standard')
        
    def save_settings(self):
        """Zapisz ustawienia"""
        config = {
            'width': self.width,
            'use_color': self.use_color,
            'outline_only': self.outline_only,
            'invert_colors': self.invert_colors,
            'brightness': self.brightness,
            'contrast': self.contrast,
            'sharpen': self.sharpen,
            'target_fps': self.target_fps,
            'show_fps': self.show_fps,
            'camera_index': self.camera_index,
            'char_set': self.char_set,
        }
        save_config(config)
        
    def configure_custom_theme(self):
        """Konfiguracja motywu zgodnego ze stroną internetową"""
        # Ustawienia kolorów dla CustomTkinter
        ctk.set_appearance_mode("dark")
        
        # Custom colors dla elementów
        self.root.configure(fg_color=self.colors['dark_bg'])
        
    def setup_ui(self):
        """Stwórz interfejs użytkownika"""
        # Główny kontener z motywem strony
        main_container = ctk.CTkFrame(self.root, fg_color=self.colors['dark_bg'], 
                                     border_color=self.colors['primary'], border_width=2)
        main_container.pack(fill="both", expand=True, padx=10, pady=10)
        
        # Lewy panel - kontrolki z motywem i scrollowaniem
        left_panel = ctk.CTkFrame(main_container, width=400, 
                                  fg_color=self.colors['darker_bg'],
                                  border_color=self.colors['border'], border_width=1)
        left_panel.pack(side="left", fill="y", padx=(0, 10))
        left_panel.pack_propagate(False)
        
        # Scrollable frame dla ustawień
        self.scrollable_frame = ctk.CTkScrollableFrame(left_panel, 
                                                       fg_color='transparent',
                                                       scrollbar_button_color=self.colors['primary'],
                                                       scrollbar_button_hover_color=self.colors['secondary'])
        self.scrollable_frame.pack(fill="both", expand=True, padx=10, pady=10)
        
        # Prawy panel - podgląd z motywem
        right_panel = ctk.CTkFrame(main_container, 
                                  fg_color=self.colors['darker_bg'],
                                  border_color=self.colors['border'], border_width=1)
        right_panel.pack(side="right", fill="both", expand=True)
        
        # === LEWY PANEL ===
        
        # Tytuł z motywem strony
        title_label = ctk.CTkLabel(self.scrollable_frame, text="🎥 ASCII CAMERA v2.0", 
                                   font=ctk.CTkFont(size=24, weight="bold"),
                                   text_color=self.colors['primary'])
        title_label.pack(pady=20)
        
        # Sekcja kamery z motywem
        camera_frame = ctk.CTkFrame(self.scrollable_frame, fg_color='transparent')
        camera_frame.pack(fill="x", padx=20, pady=10)
        
        ctk.CTkLabel(camera_frame, text="📹 KAMERA", 
                    font=ctk.CTkFont(size=18, weight="bold"),
                    text_color=self.colors['secondary']).pack(pady=10)
        
        self.start_button = ctk.CTkButton(camera_frame, text="▶ URUCHOM KAMERĘ",
                                         command=self.toggle_camera,
                                         height=40, font=ctk.CTkFont(size=14),
                                         fg_color=self.colors['primary'],
                                         hover_color=self.colors['secondary'],
                                         text_color='black')
        self.start_button.pack(pady=10, padx=20, fill="x")
        
        # Sekcja ustawień z motywem
        settings_frame = ctk.CTkFrame(self.scrollable_frame, fg_color='transparent')
        settings_frame.pack(fill="x", padx=20, pady=10)
        
        ctk.CTkLabel(settings_frame, text="⚙ USTAWIENIA", 
                    font=ctk.CTkFont(size=18, weight="bold"),
                    text_color=self.colors['secondary']).pack(pady=10)
        
        # Szerokość z motywem
        ctk.CTkLabel(settings_frame, text="Szerokość ASCII:",
                    text_color=self.colors['text_color']).pack(anchor="w", padx=20)
        self.width_var = tk.IntVar(value=self.width)
        width_slider = ctk.CTkSlider(settings_frame, from_=50, to=200, 
                                    variable=self.width_var, command=self.update_width,
                                    progress_color=self.colors['primary'],
                                    button_color=self.colors['primary'])
        width_slider.pack(fill="x", padx=20, pady=5)
        self.width_label = ctk.CTkLabel(settings_frame, text=str(self.width),
                                       text_color=self.colors['primary'])
        self.width_label.pack(anchor="w", padx=20)
        
        # Zestaw znaków z motywem
        ctk.CTkLabel(settings_frame, text="Zestaw znaków:",
                    text_color=self.colors['text_color']).pack(anchor="w", padx=20, pady=(10,0))
        self.charset_var = tk.StringVar(value=self.char_set)
        charset_menu = ctk.CTkComboBox(settings_frame, variable=self.charset_var,
                                       values=list(ASCII_SETS.keys()),
                                       command=self.update_charset,
                                       border_color=self.colors['border'],
                                       button_color=self.colors['primary'])
        charset_menu.pack(fill="x", padx=20, pady=5)
        
        # Opcje binarne z motywem
        self.color_var = tk.BooleanVar(value=self.use_color)
        color_check = ctk.CTkCheckBox(settings_frame, text="🎨 Użyj kolorów",
                                      variable=self.color_var, command=self.update_color,
                                      border_color=self.colors['primary'],
                                      checkmark_color=self.colors['primary'])
        color_check.pack(anchor="w", padx=20, pady=5)
        
        self.outline_var = tk.BooleanVar(value=self.outline_only)
        outline_check = ctk.CTkCheckBox(settings_frame, text="📝 Tylko kontur",
                                        variable=self.outline_var, command=self.update_outline,
                                        border_color=self.colors['primary'],
                                        checkmark_color=self.colors['primary'])
        outline_check.pack(anchor="w", padx=20, pady=5)
        
        self.invert_var = tk.BooleanVar(value=self.invert_colors)
        invert_check = ctk.CTkCheckBox(settings_frame, text="🔄 Odwróć kolory",
                                      variable=self.invert_var, command=self.update_invert,
                                      border_color=self.colors['primary'],
                                      checkmark_color=self.colors['primary'])
        invert_check.pack(anchor="w", padx=20, pady=5)
        
        self.sharpen_var = tk.BooleanVar(value=self.sharpen)
        sharpen_check = ctk.CTkCheckBox(settings_frame, text="✨ Wyostrzanie",
                                        variable=self.sharpen_var, command=self.update_sharpen,
                                        border_color=self.colors['primary'],
                                        checkmark_color=self.colors['primary'])
        sharpen_check.pack(anchor="w", padx=20, pady=5)
        
        # Jasność i kontrast z motywem
        ctk.CTkLabel(settings_frame, text="☀️ Jasność:",
                    text_color=self.colors['text_color']).pack(anchor="w", padx=20, pady=(10,0))
        self.brightness_var = tk.DoubleVar(value=self.brightness)
        brightness_slider = ctk.CTkSlider(settings_frame, from_=0.1, to=3.0,
                                         variable=self.brightness_var, command=self.update_brightness,
                                         progress_color=self.colors['primary'],
                                         button_color=self.colors['primary'])
        brightness_slider.pack(fill="x", padx=20, pady=5)
        self.brightness_label = ctk.CTkLabel(settings_frame, text=f"{self.brightness:.2f}",
                                            text_color=self.colors['primary'])
        self.brightness_label.pack(anchor="w", padx=20)
        
        ctk.CTkLabel(settings_frame, text="🎭 Kontrast:",
                    text_color=self.colors['text_color']).pack(anchor="w", padx=20, pady=(10,0))
        self.contrast_var = tk.DoubleVar(value=self.contrast)
        contrast_slider = ctk.CTkSlider(settings_frame, from_=0.1, to=3.0,
                                      variable=self.contrast_var, command=self.update_contrast,
                                      progress_color=self.colors['primary'],
                                      button_color=self.colors['primary'])
        contrast_slider.pack(fill="x", padx=20, pady=5)
        self.contrast_label = ctk.CTkLabel(settings_frame, text=f"{self.contrast:.2f}",
                                         text_color=self.colors['primary'])
        self.contrast_label.pack(anchor="w", padx=20)
        
        # FPS z motywem
        ctk.CTkLabel(settings_frame, text="🎬 Cel FPS:",
                    text_color=self.colors['text_color']).pack(anchor="w", padx=20, pady=(10,0))
        self.fps_var = tk.IntVar(value=self.target_fps)
        fps_slider = ctk.CTkSlider(settings_frame, from_=1, to=60,
                                 variable=self.fps_var, command=self.update_fps,
                                 progress_color=self.colors['primary'],
                                 button_color=self.colors['primary'])
        fps_slider.pack(fill="x", padx=20, pady=5)
        self.fps_label = ctk.CTkLabel(settings_frame, text=str(self.target_fps),
                                      text_color=self.colors['primary'])
        self.fps_label.pack(anchor="w", padx=20)
        
        # Profile z motywem
        profile_frame = ctk.CTkFrame(self.scrollable_frame, fg_color='transparent')
        profile_frame.pack(fill="x", padx=20, pady=10)
        
        ctk.CTkLabel(profile_frame, text="📋 PROFILE", 
                    font=ctk.CTkFont(size=18, weight="bold"),
                    text_color=self.colors['secondary']).pack(pady=10)
        
        for profile_name, profile_data in PROFILES.items():
            btn = ctk.CTkButton(profile_frame, text=f"⚡ {profile_name.upper()}",
                               command=lambda p=profile_data: self.apply_profile(p),
                               height=30, fg_color=self.colors['accent'],
                               hover_color='#990099', text_color='white')
            btn.pack(pady=2, padx=20, fill="x")
        
        # Eksport z motywem
        export_frame = ctk.CTkFrame(self.scrollable_frame, fg_color='transparent')
        export_frame.pack(fill="x", padx=20, pady=10)
        
        ctk.CTkLabel(export_frame, text="💾 EKSPORT", 
                    font=ctk.CTkFont(size=18, weight="bold"),
                    text_color=self.colors['secondary']).pack(pady=10)
        
        save_frame_btn = ctk.CTkButton(export_frame, text="📸 Zapisz klatkę",
                                      command=self.save_frame, height=30,
                                      fg_color=self.colors['primary'],
                                      hover_color=self.colors['secondary'],
                                      text_color='black')
        save_frame_btn.pack(pady=2, padx=20, fill="x")
        
        export_ascii_btn = ctk.CTkButton(export_frame, text="📄 Eksport ASCII",
                                        command=self.export_ascii, height=30,
                                        fg_color=self.colors['primary'],
                                        hover_color=self.colors['secondary'],
                                        text_color='black')
        export_ascii_btn.pack(pady=2, padx=20, fill="x")
        
        # Przycisk pełnego ekranu
        fullscreen_btn = ctk.CTkButton(export_frame, text="🖥️ Pełny ekran (F11)",
                                      command=self.toggle_fullscreen, height=30,
                                      fg_color=self.colors['accent'],
                                      hover_color='#990099',
                                      text_color='white')
        fullscreen_btn.pack(pady=2, padx=20, fill="x")
        
        # === PRAWY PANEL ===
        
        # Podgląd kamery z motywem
        preview_frame = ctk.CTkFrame(right_panel, fg_color='transparent')
        preview_frame.pack(fill="both", expand=True, padx=20, pady=20)
        
        ctk.CTkLabel(preview_frame, text="📹 PODGLĄD KAMERY", 
                    font=ctk.CTkFont(size=18, weight="bold"),
                    text_color=self.colors['secondary']).pack(pady=10)
        
        # Canvas dla obrazu z motywem
        self.canvas = tk.Canvas(preview_frame, bg=self.colors['darker_bg'], 
                               highlightthickness=2, highlightbackground=self.colors['primary'])
        self.canvas.pack(fill="both", expand=True, padx=20, pady=10)
        
        # Status bar z motywem
        self.status_frame = ctk.CTkFrame(right_panel, height=40, 
                                         fg_color=self.colors['darker_bg'],
                                         border_color=self.colors['border'], border_width=1)
        self.status_frame.pack(fill="x", padx=20, pady=(0, 20))
        self.status_frame.pack_propagate(False)
        
        self.status_label = ctk.CTkLabel(self.status_frame, text="🟢 Gotowy do pracy", 
                                         font=ctk.CTkFont(size=12),
                                         text_color=self.colors['primary'])
        self.status_label.pack(side="left", padx=20, pady=10)
        
        self.fps_label = ctk.CTkLabel(self.status_frame, text="🎬 FPS: 0", 
                                      font=ctk.CTkFont(size=12),
                                      text_color=self.colors['secondary'])
        self.fps_label.pack(side="right", padx=20, pady=10)
        
    def toggle_fullscreen(self, event=None):
        """Przełącz tryb pełnoekranowy"""
        if self.fullscreen:
            self.root.state('normal')
            self.fullscreen = False
        else:
            self.root.state('zoomed')
            self.fullscreen = True
            
    def exit_fullscreen(self):
        """Wyjdź z trybu pełnoekranowego"""
        if self.fullscreen:
            self.root.state('normal')
            self.fullscreen = False
            
    def update_width(self, value):
        """Aktualizuj szerokość"""
        self.width = int(value)
        self.width_label.configure(text=str(self.width))
        self.save_settings()
        
    def update_charset(self, value):
        """Aktualizuj zestaw znaków"""
        self.char_set = value
        self.save_settings()
        
    def update_color(self):
        """Aktualizuj użycie kolorów"""
        self.use_color = self.color_var.get()
        self.save_settings()
        
    def update_outline(self):
        """Aktualizuj tryb konturu"""
        self.outline_only = self.outline_var.get()
        self.save_settings()
        
    def update_invert(self):
        """Aktualizuj odwracanie kolorów"""
        self.invert_colors = self.invert_var.get()
        self.save_settings()
        
    def update_sharpen(self):
        """Aktualizuj wyostrzanie"""
        self.sharpen = self.sharpen_var.get()
        self.save_settings()
        
    def update_brightness(self, value):
        """Aktualizuj jasność"""
        self.brightness = float(value)
        self.brightness_label.configure(text=f"{self.brightness:.2f}")
        self.save_settings()
        
    def update_contrast(self, value):
        """Aktualizuj kontrast"""
        self.contrast = float(value)
        self.contrast_label.configure(text=f"{self.contrast:.2f}")
        self.save_settings()
        
    def update_fps(self, value):
        """Aktualizuj cel FPS"""
        self.target_fps = int(value)
        self.fps_label.configure(text=str(self.target_fps))
        self.save_settings()
        
    def apply_profile(self, profile: Dict[str, Any]):
        """Zastosuj profil ustawień"""
        self.width = profile.get('width', self.width)
        self.char_set = profile.get('char_set', self.char_set)
        self.use_color = profile.get('use_color', self.use_color)
        self.outline_only = profile.get('outline_only', self.outline_only)
        self.invert_colors = profile.get('invert_colors', self.invert_colors)
        self.brightness = profile.get('brightness', self.brightness)
        self.contrast = profile.get('contrast', self.contrast)
        self.sharpen = profile.get('sharpen', self.sharpen)
        self.target_fps = profile.get('target_fps', self.target_fps)
        
        # Aktualizuj UI
        self.width_var.set(self.width)
        self.width_label.configure(text=str(self.width))
        self.charset_var.set(self.char_set)
        self.color_var.set(self.use_color)
        self.outline_var.set(self.outline_only)
        self.invert_var.set(self.invert_colors)
        self.sharpen_var.set(self.sharpen)
        self.brightness_var.set(self.brightness)
        self.brightness_label.configure(text=f"{self.brightness:.2f}")
        self.contrast_var.set(self.contrast)
        self.contrast_label.configure(text=f"{self.contrast:.2f}")
        self.fps_var.set(self.target_fps)
        self.fps_label.configure(text=str(self.target_fps))
        
        self.save_settings()
        self.status_label.configure(text=f"Załadowano profil")
        
    def toggle_camera(self):
        """Przełącz stan kamery"""
        if self.camera_running:
            self.stop_camera()
        else:
            self.start_camera()
            
    def start_camera(self):
        """Uruchom kamerę"""
        try:
            cap = cv2.VideoCapture(self.camera_index)
            if not cap.isOpened():
                messagebox.showerror("Błąd", "Nie udało się uruchomić kamery!")
                return
            cap.release()
            
            self.camera_running = True
            self.start_button.configure(text="⏹ ZATRZYMAJ KAMERĘ")
            self.status_label.configure(text="Kamera uruchomiona")
            
            # Start wątku kamery
            self.camera_thread = threading.Thread(target=self.camera_loop, daemon=True)
            self.camera_thread.start()
            
        except Exception as e:
            messagebox.showerror("Błąd", f"Błąd uruchamiania kamery: {e}")
            
    def stop_camera(self):
        """Zatrzymaj kamerę"""
        self.camera_running = False
        self.start_button.configure(text="▶ URUCHOM KAMERĘ")
        self.status_label.configure(text="Kamera zatrzymana")
        
    def camera_loop(self):
        """Główna pętla kamery w osobnym wątku"""
        cap = cv2.VideoCapture(self.camera_index)
        cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
        
        last_time = time.time()
        target_frame_time = 1.0 / self.target_fps
        
        try:
            while self.camera_running:
                ret, frame = cap.read()
                if not ret:
                    break
                    
                current_time = time.time()
                elapsed = current_time - last_time
                
                if elapsed >= target_frame_time:
                    # Konwersja na ASCII
                    self.ascii_text = frame_to_ascii(
                        frame, self.width, self.use_color, self.outline_only,
                        self.invert_colors, self.brightness, self.contrast,
                        self.sharpen, self.char_set
                    )
                    
                    # Zapisz klatkę do wyświetlenia
                    if not self.frame_queue.full():
                        self.frame_queue.put(frame)
                    
                    # Aktualizuj FPS
                    self.fps_counter += 1
                    if current_time - self.fps_start_time >= 1.0:
                        self.current_fps = self.fps_counter
                        self.fps_counter = 0
                        self.fps_start_time = current_time
                    
                    last_time = current_time
                    
                time.sleep(0.001)  # Małe opóźnienie
                
        except Exception as e:
            print(f"Błąd w pętli kamery: {e}")
        finally:
            cap.release()
            
    def update_display(self):
        """Aktualizuj wyświetlanie"""
        try:
            # Pobierz klatkę z kolejki
            if not self.frame_queue.empty():
                frame = self.frame_queue.get_nowait()
                self.display_frame(frame)
                
                # Wyświetl ASCII na canvasie
                self.display_ascii()
                
                # Aktualizuj FPS
                if self.show_fps:
                    self.fps_label.configure(text=f"FPS: {self.current_fps}")
                    
        except queue.Empty:
            pass
        except Exception as e:
            print(f"Błąd aktualizacji wyświetlania: {e}")
            
        # Zaplanuj kolejną aktualizację
        self.root.after(30, self.update_display)
        
    def display_frame(self, frame):
        """Wyświetl klatkę na canvasie"""
        try:
            # Konwersja na RGB i zmiana rozmiaru
            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            h, w = frame_rgb.shape[:2]
            
            # Dostosuj rozmiar do canvasa
            canvas_width = self.canvas.winfo_width()
            canvas_height = self.canvas.winfo_height()
            
            if canvas_width > 1 and canvas_height > 1:
                ratio = min(canvas_width / w, canvas_height / h)
                new_w = int(w * ratio)
                new_h = int(h * ratio)
                
                frame_resized = cv2.resize(frame_rgb, (new_w, new_h))
                
                # Konwersja na PIL Image
                image = Image.fromarray(frame_resized)
                photo = ImageTk.PhotoImage(image=image)
                
                # Wyświetl na canvasie
                self.canvas.delete("all")
                x = (canvas_width - new_w) // 2
                y = (canvas_height - new_h) // 2
                self.canvas.create_image(x, y, anchor="nw", image=photo)
                self.canvas.image = photo  # Zapobiegaj garbage collection
                
        except Exception as e:
            print(f"Błąd wyświetlania klatki: {e}")
            
    def display_ascii(self):
        """Wyświetl tekst ASCII na canvasie"""
        try:
            if self.ascii_text:
                # Wyczyść canvas
                self.canvas.delete("all")
                
                # Ustaw czcionkę monospace
                font = ("Courier", 8)
                
                # Wyświetl tekst z obsługą kolorów
                lines = self.ascii_text.split('\n')
                y = 10
                
                for line in lines[:50]:  # Ogranicz liczbę linii
                    if self.use_color and '\033[' in line:
                        # Obsługa kolorów ANSI - rozdziel na segmenty
                        segments = self.parse_ansi_colors(line)
                        x = 10
                        for text, color in segments:
                            if color:
                                self.canvas.create_text(x, y, anchor="nw", text=text, 
                                                       font=font, fill=color)
                            else:
                                self.canvas.create_text(x, y, anchor="nw", text=text, 
                                                       font=font, fill=self.colors['primary'])
                            # Oblicz szerokość tekstu
                            x += len(text) * 5  # Szacunkowa szerokość znaku
                    else:
                        # Bez kolorów - zwykły tekst
                        self.canvas.create_text(10, y, anchor="nw", text=line, 
                                               font=font, fill=self.colors['primary'])
                    y += 10
                    
        except Exception as e:
            print(f"Błąd wyświetlania ASCII: {e}")
            
    def parse_ansi_colors(self, text: str) -> list:
        """Parsuj tekst z kodami ANSI kolorów"""
        segments = []
        current_color = None
        i = 0
        
        while i < len(text):
            if text.startswith('\033[', i):
                # Znajdź koniec sekwencji ANSI
                end = text.find('m', i)
                if end != -1:
                    # Parsuj kod koloru
                    color_code = text[i+2:end]
                    if color_code.startswith('38;2;'):
                        # RGB color
                        rgb_parts = color_code.split(';')
                        if len(rgb_parts) >= 5:
                            r = int(rgb_parts[2])
                            g = int(rgb_parts[3]) 
                            b = int(rgb_parts[4])
                            current_color = f'#{r:02x}{g:02x}{b:02x}'
                    i = end + 1
                else:
                    i += 1
            else:
                # Zbierz zwykły tekst
                start = i
                while i < len(text) and not text.startswith('\033[', i):
                    i += 1
                segments.append((text[start:i], current_color))
                
        return segments
            
    def save_frame(self):
        """Zapisz aktualną klatkę"""
        if not self.frame_queue.empty():
            try:
                frame = self.frame_queue.get()
                filename = RECORDINGS_DIR / f"frame_{get_timestamp()}.png"
                cv2.imwrite(str(filename), frame)
                messagebox.showinfo("Sukces", f"Zapisano: {filename.name}")
                self.status_label.configure(text=f"Zapisano klatkę")
            except Exception as e:
                messagebox.showerror("Błąd", f"Błąd zapisu klatki: {e}")
        else:
            messagebox.showwarning("Ostrzeżenie", "Brak dostępnej klatki")
            
    def export_ascii(self):
        """Eksportuj tekst ASCII"""
        if self.ascii_text:
            try:
                filename = RECORDINGS_DIR / f"ascii_{get_timestamp()}.txt"
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write(self.ascii_text)
                messagebox.showinfo("Sukces", f"Eksportowano: {filename.name}")
                self.status_label.configure(text=f"Eksportowano ASCII")
            except Exception as e:
                messagebox.showerror("Błąd", f"Błąd eksportu ASCII: {e}")
        else:
            messagebox.showwarning("Ostrzeżenie", "Brak dostępnych danych ASCII")
            
    def run(self):
        """Uruchom aplikację"""
        try:
            self.root.mainloop()
        except KeyboardInterrupt:
            self.stop_camera()
        finally:
            self.save_settings()

# ==================== PUNKT WEJŚCIA ====================

def main():
    """Główna funkcja aplikacji"""
    try:
        # Sprawdź zależności
        try:
            import customtkinter
            import PIL
        except ImportError as e:
            print(f"Brakujące zależności: {e}")
            print("Zainstaluj: pip install customtkinter pillow")
            return
            
        # Uruchom GUI
        app = ASCIICameraGUI()
        app.run()
        
    except Exception as e:
        print(f"Krytyczny błąd: {e}")

if __name__ == "__main__":
    main()