import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
import json
import subprocess
import os
import sys

# Versuche die Icon-Bibliotheken zu importieren
try:
    import win32ui
    import win32gui
    import win32con
    import win32api
    from PIL import Image, ImageTk
    ICON_SUPPORT = True
except ImportError:
    # Wir können messagebox hier nicht verwenden, da es aus tkinter importiert wird
    print("Warnung: Die Bibliotheken für Programm-Icons fehlen. Installieren Sie diese mit: pip install pywin32 Pillow")
    ICON_SUPPORT = False

def resource_path(relative_path):
    """Get absolute path to resource, works for dev and for PyInstaller"""
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

def extract_icon_from_exe(exe_path):
    """Extrahiert das Icon aus einer EXE-Datei"""
    if not ICON_SUPPORT:
        return None
        
    try:
        # Icon-Handle erhalten
        large, small = win32gui.ExtractIconEx(exe_path, 0)
        
        # Wir verwenden das größere Icon
        if large:
            # Wir nehmen das erste Icon
            ico_x = win32api.GetSystemMetrics(win32con.SM_CXICON)
            ico_y = win32api.GetSystemMetrics(win32con.SM_CYICON)
            
            # Icon in DC (Device Context) zeichnen
            hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
            hbmp = win32ui.CreateBitmap()
            hbmp.CreateCompatibleBitmap(hdc, ico_x, ico_y)
            hdc = hdc.CreateCompatibleDC()
            
            hdc.SelectObject(hbmp)
            hdc.DrawIcon((0, 0), large[0])
            
            # Bitmap in ein Python-Image-Objekt umwandeln
            bmpinfo = hbmp.GetInfo()
            bmpstr = hbmp.GetBitmapBits(True)
            img = Image.frombuffer(
                'RGBA',
                (bmpinfo['bmWidth'], bmpinfo['bmHeight']),
                bmpstr, 'raw', 'BGRA', 0, 1
            )
            
            # Aufräumen
            win32gui.DestroyIcon(large[0])
            for icon in small:
                if icon:
                    win32gui.DestroyIcon(icon)
                    
            # Bild auf die gewünschte Größe skalieren
            img = img.resize((16, 16), Image.LANCZOS)
            
            # In PhotoImage umwandeln für Tkinter
            return ImageTk.PhotoImage(img)
        return None
    except Exception as e:
        print(f"Fehler beim Extrahieren des Icons: {str(e)}")
        return None

class ProgramLauncher:
    def __init__(self, root):
        self.root = root
        self.root.title("Programm-Shortcut")
        self.root.geometry("700x550")
        self.root.minsize(600, 450)
        self.root.configure(bg="#f0f0f0")
        
        # Lade das Symbol, falls vorhanden
        try:
            self.root.iconbitmap(resource_path("icon.ico"))
        except:
            pass
        
        # Initialisiere Standardwerte
        self.programs = []
        self.categories = ["Spiele", "Werkzeuge", "Office", "Multimedia", "Internet"]
        self.options = self.get_default_options()
        self.sort_column = None
        self.sort_reverse = False
        
        # Icon-Cache initialisieren
        self.icon_cache = {}
        
        # Laden der Daten aus der JSON-Datei
        self.json_file = 'data.json'
        self.load_data()
        
        self.create_widgets()
        self.populate_program_list()
        
        # Wende gespeicherte Optionen an
        self.apply_options()
        
        # Zentriere das Fenster
        self.center_window()
        
    def center_window(self):
        self.root.update_idletasks()
        width = self.root.winfo_width()
        height = self.root.winfo_height()
        x = (self.root.winfo_screenwidth() // 2) - (width // 2)
        y = (self.root.winfo_screenheight() // 2) - (height // 2)
        self.root.geometry(f"{width}x{height}+{x}+{y}")
    
    def load_data(self):
        """Lädt Programme und Kategorien aus der JSON-Datei"""
        try:
            # Prüfen, ob die alte Datei existiert und umbenannt werden muss
            if not os.path.exists(self.json_file) and os.path.exists('program_list.json'):
                # Wandle altes in neues Format um und speichere es
                with open('program_list.json', encoding='utf-8') as f:
                    old_data = json.load(f)
                
                # Wenn es sich um das alte Format handelt (nur Liste von Programmen)
                if isinstance(old_data, list):
                    self.data = {
                        "categories": self.categories,
                        "programs": old_data,
                        "options": self.get_default_options()
                    }
                    # Kategorien aus Programmen extrahieren und mit Standardkategorien zusammenführen
                    for program in old_data:
                        if "Kategorie" in program and program["Kategorie"] not in self.data["categories"]:
                            self.data["categories"].append(program["Kategorie"])
                else:
                    # Wenn es bereits das neue Format hat, füge nur Optionen hinzu
                    old_data["options"] = self.get_default_options()
                    self.data = old_data
                
                # Speichere in neuer Datei
                with open(self.json_file, 'w', encoding='utf-8') as f:
                    json.dump(self.data, f, indent=2, ensure_ascii=False)
            elif not os.path.exists(self.json_file):
                # Erstelle eine neue Datei mit leeren Werten und Standardoptionen
                self.data = {
                    "categories": self.categories,
                    "programs": [],
                    "options": self.get_default_options()
                }
                self.save_data()
            else:
                # Datei laden
                with open(self.json_file, encoding='utf-8') as f:
                    data = json.load(f)
                    
                # Überprüfe, ob es sich um das alte Format handelt (ohne options)
                if "options" not in data:
                    data["options"] = self.get_default_options()
                    
                # Verwende das neue Format
                self.data = data
                
                # Aktualisiere die Werte für den einfacheren Zugriff
                self.programs = self.data["programs"]
                self.categories = self.data["categories"]
                self.options = self.data.get("options", self.get_default_options())
            
        except (FileNotFoundError, json.JSONDecodeError) as e:
            messagebox.showwarning("Warnung", f"Fehler beim Laden der Daten: {str(e)}\nEine neue Liste wird erstellt.")
            self.data = {
                "categories": self.categories,
                "programs": [],
                "options": self.get_default_options()
            }
            # Speichere die neue Liste
            self.save_data()
            
        # Stelle sicher, dass die Variablen immer richtig gesetzt sind
        self.programs = self.data["programs"]
        self.categories = self.data["categories"]
        self.options = self.data["options"]
            
    def get_default_options(self):
        """Gibt Standardoptionen zurück"""
        return {
            "default_sort_column": None,
            "default_sort_reverse": False,
            "remember_last_category": False,
            "last_category": "Alle",
            "window_width": 700,
            "window_height": 550
        }
    
    def apply_options(self):
        """Wendet gespeicherte Optionen an"""
        # Wenn eine Standardsortierung gespeichert ist, anwenden
        if self.options.get("default_sort_column"):
            self.sort_column = self.options["default_sort_column"]
            self.sort_reverse = self.options["default_sort_reverse"]
            self.populate_program_list()
        
        # Wenn die letzte Kategorie gespeichert werden soll, auswählen
        if self.options.get("remember_last_category") and self.options.get("last_category"):
            # Finde die Kategorie in der Listbox
            for i in range(self.category_listbox.size()):
                if self.category_listbox.get(i) == self.options["last_category"]:
                    self.category_listbox.selection_clear(0, tk.END)
                    self.category_listbox.selection_set(i)
                    self.on_category_select(None)
                    break
        
        # Fenstergröße anwenden, falls gespeichert
        if self.options.get("window_width") and self.options.get("window_height"):
            self.root.geometry(f"{self.options['window_width']}x{self.options['window_height']}")
    
    def save_data(self):
        """Speichert Programme und Kategorien in der JSON-Datei"""
        try:
            # Aktuelle Optionen aktualisieren
            if hasattr(self, 'sort_column'):
                self.options["default_sort_column"] = self.sort_column
                self.options["default_sort_reverse"] = self.sort_reverse
            
            # Fenstergröße speichern
            self.options["window_width"] = self.root.winfo_width()
            self.options["window_height"] = self.root.winfo_height()
            
            # Aktuelle Kategorie speichern, wenn Option aktiviert ist
            if self.options.get("remember_last_category"):
                try:
                    index = self.category_listbox.curselection()[0]
                    self.options["last_category"] = self.category_listbox.get(index)
                except (IndexError, AttributeError):
                    pass
            
            # Stelle sicher, dass die Optionen im Hauptdatenobjekt aktualisiert werden
            self.data["options"] = self.options
            
            # In Datei speichern
            with open(self.json_file, 'w', encoding='utf-8') as f:
                json.dump(self.data, f, indent=2, ensure_ascii=False)
                
            # Debug: Dateiinhalt überprüfen
            # print(f"Saved options: {self.options}")
        except Exception as e:
            messagebox.showerror("Fehler", f"Fehler beim Speichern der Daten: {str(e)}")
    
    def create_widgets(self):
        # Hauptframe
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # Stil konfigurieren
        style = ttk.Style()
        style.configure("TFrame", background="#f0f0f0")
        style.configure("TButton", background="#4CAF50", foreground="black", padding=5)
        style.configure("TLabel", background="#f0f0f0", foreground="#333333", font=("Segoe UI", 10))
        style.configure("Title.TLabel", background="#f0f0f0", foreground="#333333", font=("Segoe UI", 14, "bold"))
        style.configure("Category.TLabel", background="#f0f0f0", foreground="#555555", font=("Segoe UI", 12, "bold"))
        
        # Titel
        title_label = ttk.Label(main_frame, text="Programm-Shortcut", style="Title.TLabel")
        title_label.pack(pady=10)
        
        # Erklärungstext
        help_text = ttk.Label(main_frame, text="Wählen Sie ein Programm zum Starten oder fügen Sie ein neues hinzu")
        help_text.pack(pady=5)
        
        # Horizontaler Frame für Kategorieauswahl und Programmliste
        h_frame = ttk.Frame(main_frame)
        h_frame.pack(fill=tk.BOTH, expand=True, pady=10)
        
        # Linker Frame für Kategorien (vertikal ausgerichtet)
        category_frame = ttk.Frame(h_frame, padding="5")
        category_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
        
        # Container für Kategorien-Bereich als vertikalen Stack
        category_content = ttk.Frame(category_frame)
        category_content.pack(fill=tk.BOTH, expand=True)
        
        # Kategorie-Label
        cat_label = ttk.Label(category_content, text="Kategorien", style="Category.TLabel")
        cat_label.pack(pady=(0, 5), anchor="w")
        
        # Container für Listbox und Scrollbar
        list_container = ttk.Frame(category_content)
        list_container.pack(fill=tk.BOTH, expand=True)
        
        # Scrollbar für Kategorieliste
        cat_scrollbar = ttk.Scrollbar(list_container)
        cat_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # Listbox für Kategorien
        self.category_listbox = tk.Listbox(list_container, height=15, width=15, 
                                          yscrollcommand=cat_scrollbar.set,
                                          font=("Segoe UI", 10),
                                          selectbackground="#4CAF50",
                                          activestyle="none",
                                          exportselection=0)
        self.category_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        cat_scrollbar.config(command=self.category_listbox.yview)
        
        # Füllen der Kategorie-Listbox
        for category in sorted(self.categories):
            self.category_listbox.insert(tk.END, category)
            
        # Standardmäßig "Alle" auswählen
        self.category_listbox.insert(0, "Alle")
        self.category_listbox.selection_set(0)
        self.category_listbox.bind("<<ListboxSelect>>", self.on_category_select)
        
        # Kategorie-Buttons-Container erstellen (unterhalb der Listbox)
        button_container = ttk.Frame(category_content)
        button_container.pack(fill=tk.X, pady=10)
        
        # Container für die Buttons, damit sie zentriert werden können
        buttons_center = ttk.Frame(button_container)
        buttons_center.pack(anchor=tk.CENTER)
        
        # Die Buttons nebeneinander anordnen
        self.add_cat_button = ttk.Button(buttons_center, text="+", width=3, command=self.add_category)
        self.add_cat_button.pack(side=tk.LEFT, padx=5)
        
        self.remove_cat_button = ttk.Button(buttons_center, text="-", width=3, command=self.remove_category)
        self.remove_cat_button.pack(side=tk.LEFT, padx=5)
        
        # Rechter Frame für Programme
        program_frame = ttk.Frame(h_frame)
        program_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # Frame für Programmliste und Scrollen
        list_frame = ttk.Frame(program_frame)
        list_frame.pack(fill=tk.BOTH, expand=True)
        
        # Scrollbar für die Treeview
        scrollbar = ttk.Scrollbar(list_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # Anzeige von Icons aktivieren und Platz dafür schaffen
        style.configure("Treeview", rowheight=24)  # Mehr Platz für Icons
        
        # Statt ein Zellenpadding zu verwenden, verwenden wir einen zusätzlichen Abstand im Text
        style.configure("Treeview.Cell", padding=(0, 0))
        
        # Treeview für Programme
        # Wenn Icon-Unterstützung aktiviert ist, verwenden wir ein anderes Format
        if ICON_SUPPORT:
            self.program_tree = ttk.Treeview(list_frame, yscrollcommand=scrollbar.set, 
                                           columns=("name", "category"), 
                                           show="tree headings")
        else:
            self.program_tree = ttk.Treeview(list_frame, yscrollcommand=scrollbar.set, 
                                           columns=("name", "category"), 
                                           show="headings")
        
        # Setze Spaltenüberschriften mit Sortierfunktion
        self.program_tree.heading("name", text="Programm", command=lambda: self.sort_treeview("name", False))
        self.program_tree.heading("category", text="Kategorie", command=lambda: self.sort_treeview("category", False))
        
        # Konfiguriere Spaltenbreiten und Ausrichtung
        self.program_tree.column("name", width=220, anchor="w", minwidth=150)
        self.program_tree.column("category", width=100, anchor="w")
        self.program_tree.column("#0", width=30, stretch=False, anchor="w")  # Icon-Spalte
        
        self.program_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        scrollbar.config(command=self.program_tree.yview)
        
        # Frame für Buttons
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=10)
        
        # Buttons
        self.start_button = ttk.Button(button_frame, text="Starten", command=self.start_program)
        self.start_button.pack(side=tk.LEFT, padx=5)
        
        self.add_button = ttk.Button(button_frame, text="Hinzufügen", command=self.add_program)
        self.add_button.pack(side=tk.LEFT, padx=5)
        
        self.remove_button = ttk.Button(button_frame, text="Entfernen", command=self.remove_program)
        self.remove_button.pack(side=tk.LEFT, padx=5)
        
        self.edit_button = ttk.Button(button_frame, text="Bearbeiten", command=self.edit_program)
        self.edit_button.pack(side=tk.LEFT, padx=5)
        
        self.options_button = ttk.Button(button_frame, text="Optionen", command=self.show_options_dialog)
        self.options_button.pack(side=tk.RIGHT, padx=5)
        
        # Doppelklick auf einen Eintrag
        self.program_tree.bind("<Double-1>", lambda event: self.start_program())
        
        # Sortierungszustand
        self.sort_column = self.options.get("default_sort_column")
        self.sort_reverse = self.options.get("default_sort_reverse", False)
    
    def show_options_dialog(self):
        """Zeigt einen Dialog mit Programmoptionen an"""
        options_window = tk.Toplevel(self.root)
        options_window.title("Programmoptionen")
        options_window.geometry("450x350")
        options_window.resizable(False, False)
        options_window.grab_set()  # Modal machen
        
        # Zentriere das Dialogfenster
        options_window.update_idletasks()
        width = options_window.winfo_width()
        height = options_window.winfo_height()
        x = (options_window.winfo_screenwidth() // 2) - (width // 2)
        y = (options_window.winfo_screenheight() // 2) - (height // 2)
        options_window.geometry(f"{width}x{height}+{x}+{y}")
        
        # Frame
        frame = ttk.Frame(options_window, padding="20")
        frame.pack(fill=tk.BOTH, expand=True)
        
        # Überschrift
        ttk.Label(frame, text="Programmoptionen", font=("Segoe UI", 12, "bold")).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 15))
        
        # Option für Standardsortierung
        ttk.Label(frame, text="Standardsortierung:").grid(row=1, column=0, sticky="w", pady=5)
        sort_var = tk.StringVar(value=self.get_sort_option_text())
        sort_combobox = ttk.Combobox(frame, textvariable=sort_var, values=[
            "Keine", 
            "Programm (A-Z)", 
            "Programm (Z-A)", 
            "Kategorie (A-Z)", 
            "Kategorie (Z-A)"
        ])
        sort_combobox.grid(row=1, column=1, sticky="we", pady=5)
        sort_combobox.configure(state="readonly")
        
        # Option zum Speichern der letzten Kategorie
        remember_category_var = tk.BooleanVar(value=self.options.get("remember_last_category", False))
        remember_category_check = ttk.Checkbutton(
            frame, 
            text="Letzte ausgewählte Kategorie merken", 
            variable=remember_category_var
        )
        remember_category_check.grid(row=2, column=0, columnspan=2, sticky="w", pady=5)
        
        # Trennlinie
        separator = ttk.Separator(frame, orient="horizontal")
        separator.grid(row=3, column=0, columnspan=2, sticky="ew", pady=10)
        
        # Info-Bereich
        info_frame = ttk.Frame(frame)
        info_frame.grid(row=4, column=0, columnspan=2, sticky="ew", pady=5)
        
        # Version
        version_label = ttk.Label(info_frame, text="Version: 1.1", font=("Segoe UI", 9))
        version_label.pack(anchor="w")
        
        # Copyright & Autor Info
        author_label = ttk.Label(info_frame, text="© 2025 Akamaru", font=("Segoe UI", 9))
        author_label.pack(anchor="w")
        
        created_label = ttk.Label(info_frame, text="Erstellt mit Hilfe von Claude", font=("Segoe UI", 9))
        created_label.pack(anchor="w")
        
        # Link zum Quellcode mit Unterstreichung
        link_style = ttk.Style()
        link_style.configure("Link.TLabel", foreground="blue", font=("Segoe UI", 9, "underline"))
        
        link_label = ttk.Label(info_frame, text="Quellcode auf git.ponywave.de verfügbar", 
                             style="Link.TLabel", cursor="hand2")
        link_label.pack(anchor="w", pady=(5, 0))
        link_label.bind("<Button-1>", lambda e: self.open_url("https://git.ponywave.de/Akamaru/Programm-Shortcut"))
        
        # Buttons
        button_frame = ttk.Frame(frame)
        button_frame.grid(row=5, column=0, columnspan=2, pady=20)
        
        save_button = ttk.Button(
            button_frame, 
            text="Speichern", 
            command=lambda: self.save_options(
                sort_combobox.get(),
                remember_category_var.get(),
                options_window
            )
        )
        save_button.pack(side=tk.LEFT, padx=5)
        
        cancel_button = ttk.Button(button_frame, text="Abbrechen", command=options_window.destroy)
        cancel_button.pack(side=tk.LEFT, padx=5)
    
    def get_sort_option_text(self):
        """Gibt den Text für die aktuelle Sortierungsoption zurück"""
        if self.sort_column == "name":
            if self.sort_reverse:
                return "Programm (Z-A)"
            else:
                return "Programm (A-Z)"
        elif self.sort_column == "category":
            if self.sort_reverse:
                return "Kategorie (Z-A)"
            else:
                return "Kategorie (A-Z)"
        else:
            return "Keine"
    
    def save_options(self, sort_option, remember_category, window):
        """Speichert die Programmoptionen"""
        # Sortieroptionen übersetzen
        if sort_option == "Programm (A-Z)":
            self.options["default_sort_column"] = "name"
            self.options["default_sort_reverse"] = False
            self.sort_column = "name"
            self.sort_reverse = False
        elif sort_option == "Programm (Z-A)":
            self.options["default_sort_column"] = "name"
            self.options["default_sort_reverse"] = True
            self.sort_column = "name"
            self.sort_reverse = True
        elif sort_option == "Kategorie (A-Z)":
            self.options["default_sort_column"] = "category"
            self.options["default_sort_reverse"] = False
            self.sort_column = "category"
            self.sort_reverse = False
        elif sort_option == "Kategorie (Z-A)":
            self.options["default_sort_column"] = "category"
            self.options["default_sort_reverse"] = True
            self.sort_column = "category"
            self.sort_reverse = True
        else:  # "Keine"
            self.options["default_sort_column"] = None
            self.options["default_sort_reverse"] = False
            self.sort_column = None
            self.sort_reverse = False
        
        # Kategorie-Speicher-Option setzen
        self.options["remember_last_category"] = remember_category
        
        # Aktuelle Kategorie speichern, wenn Option aktiviert ist
        if remember_category:
            try:
                index = self.category_listbox.curselection()[0]
                self.options["last_category"] = self.category_listbox.get(index)
            except (IndexError, AttributeError):
                pass
        
        # Stelle sicher, dass die Optionen im Hauptdatenobjekt aktualisiert werden
        self.data["options"] = self.options
        
        # Änderungen speichern
        self.save_data()
        
        # Liste neu sortieren
        self.populate_program_list()
        
        # Fenster schließen
        window.destroy()
    
    def open_url(self, url):
        """Öffnet eine URL im Standard-Browser"""
        try:
            import webbrowser
            webbrowser.open(url)
        except Exception as e:
            messagebox.showerror("Fehler", f"Fehler beim Öffnen der URL: {str(e)}")
    
    def on_category_select(self, event):
        # Aktualisiere die Programmliste basierend auf der ausgewählten Kategorie
        self.populate_program_list()
        
    def add_category(self):
        """Fügt eine neue Kategorie hinzu"""
        # Dialog für neue Kategorie
        category_name = simpledialog.askstring("Neue Kategorie", "Geben Sie den Namen der neuen Kategorie ein:")
        if not category_name:
            return
            
        # Prüfe, ob die Kategorie bereits existiert
        if category_name in self.categories:
            messagebox.showwarning("Warnung", f"Die Kategorie '{category_name}' existiert bereits.")
            return
            
        # Füge die Kategorie zur Liste hinzu
        self.categories.append(category_name)
        
        # Aktualisiere die Kategorie-Listbox
        self.category_listbox.delete(1, tk.END)  # Lösche alle außer "Alle"
        for category in sorted(self.categories):
            self.category_listbox.insert(tk.END, category)
            
        # Speichere die aktualisierte Kategorienliste
        self.save_data()
    
    def remove_category(self):
        """Entfernt eine Kategorie"""
        # Hole die ausgewählte Kategorie
        try:
            index = self.category_listbox.curselection()[0]
            category = self.category_listbox.get(index)
        except IndexError:
            messagebox.showinfo("Information", "Bitte wählen Sie eine Kategorie zum Entfernen aus.")
            return
            
        # "Alle" kann nicht entfernt werden
        if category == "Alle":
            messagebox.showinfo("Information", "Die Kategorie 'Alle' kann nicht entfernt werden.")
            return
            
        # Bestätigung einholen
        confirm = messagebox.askyesno("Bestätigung", 
                                      f"Möchten Sie die Kategorie '{category}' wirklich entfernen?\n\n"
                                      "Alle Programme in dieser Kategorie werden ohne Kategorie gespeichert.")
        if not confirm:
            return
            
        # Programme ohne Kategorie speichern
        for program in self.programs:
            if program.get("Kategorie") == category:
                if "Kategorie" in program:
                    del program["Kategorie"]
                
        # Entferne die Kategorie aus der Liste
        self.categories.remove(category)
        
        # Aktualisiere die Kategorie-Listbox
        self.category_listbox.delete(1, tk.END)  # Lösche alle außer "Alle"
        for category in sorted(self.categories):
            self.category_listbox.insert(tk.END, category)
            
        # Wähle "Alle" aus
        self.category_listbox.selection_set(0)
        
        # Speichere die Änderungen
        self.save_data()
        self.populate_program_list()
    
    def populate_program_list(self):
        # Lösche alle bestehenden Einträge
        for item in self.program_tree.get_children():
            self.program_tree.delete(item)
        
        # Hole die ausgewählte Kategorie
        try:
            index = self.category_listbox.curselection()[0]
            selected_category = self.category_listbox.get(index)
        except IndexError:
            selected_category = "Alle"
        
        # Sortiere Programme nach Kategorie und Name
        def get_category(program):
            return program.get("Kategorie", "").lower() if "Kategorie" in program else ""
        
        # Nach dem aktuellen Sortierzustand sortieren
        if self.sort_column == "name":
            # Nach Programmname sortieren (Groß-/Kleinschreibung ignorieren)
            sorted_programs = sorted(self.programs, key=lambda x: x["Name"].lower(), reverse=self.sort_reverse)
        elif self.sort_column == "category":
            # Nach Kategorie sortieren (Groß-/Kleinschreibung ignorieren)
            sorted_programs = sorted(self.programs, key=lambda x: get_category(x), reverse=self.sort_reverse)
        else:
            # Standardsortierung: Kategorie dann Name (Groß-/Kleinschreibung ignorieren)
            sorted_programs = sorted(self.programs, key=lambda x: (get_category(x), x["Name"].lower()))
        
        # Füge Programme hinzu
        for program in sorted_programs:
            category = program.get("Kategorie", "") if "Kategorie" in program else ""
            
            # Filtere nach Kategorie, wenn nicht "Alle" ausgewählt ist
            if selected_category != "Alle" and category != selected_category:
                continue
            
            program_path = program["Pfad"]
            program_name = "   " + program["Name"]  # Fester Abstand mit 3 Leerzeichen
            
            # Hole das Icon nur, wenn Icon-Unterstützung aktiviert ist
            icon = None
            if ICON_SUPPORT:
                # Hole das Icon, wenn es noch nicht im Cache ist
                if program_path in self.icon_cache:
                    icon = self.icon_cache[program_path]
                else:
                    # Versuche, das Icon zu extrahieren
                    icon = extract_icon_from_exe(program_path)
                    # Speichere im Cache für zukünftige Verwendung
                    self.icon_cache[program_path] = icon
            
            # Füge Programm hinzu
            if ICON_SUPPORT and icon:
                # Mit Icon in der #0 Spalte
                item_id = self.program_tree.insert("", tk.END, text="", values=(program_name, category), 
                                                 tags=(program_path,), image=icon)
            else:
                # Ohne Icon, normaler Eintrag
                item_id = self.program_tree.insert("", tk.END, text="", values=(program_name, category), 
                                                 tags=(program_path,))
    
    def start_program(self):
        selected_items = self.program_tree.selection()
        if not selected_items:
            messagebox.showinfo("Information", "Bitte wählen Sie ein Programm aus.")
            return
        
        item_id = selected_items[0]
        program_values = self.program_tree.item(item_id, "values")
        program_name = program_values[0].strip()  # Leerzeichen am Anfang entfernen
        program_path = self.program_tree.item(item_id, "tags")[0]
        
        # Prüfen, ob das Programm Adminrechte benötigt
        requires_admin = False
        for program in self.programs:
            if program["Name"].strip() == program_name.strip():
                requires_admin = program.get("Adminrechte", False)
                break
        
        try:
            if sys.platform == "win32" and requires_admin:
                try:
                    # Direkter Aufruf über ShellExecute API mit erhöhten Rechten
                    import ctypes
                    # Der Parameter "runas" fordert erhöhte Rechte an
                    result = ctypes.windll.shell32.ShellExecuteW(None, "runas", program_path, None, None, 1)
                    # Wenn der Rückgabewert <= 32 ist, gab es einen Fehler
                    if result <= 32:
                        # Fallback: Starte normal ohne erhöhte Rechte
                        subprocess.Popen(program_path)
                except Exception:
                    # Wenn es fehlschlägt, starte normal
                    subprocess.Popen(program_path)
            else:
                # Normaler Start ohne Adminrechte
                subprocess.Popen(program_path)
        except Exception as e:
            messagebox.showerror("Fehler", f"Fehler beim Starten des Programms: {str(e)}")
    
    def add_program(self):
        add_window = tk.Toplevel(self.root)
        add_window.title("Programm hinzufügen")
        add_window.geometry("450x280")  # Etwas mehr Platz für die Checkbox
        add_window.resizable(False, False)
        add_window.grab_set()  # Modal machen
        
        # Zentriere das Dialogfenster
        add_window.update_idletasks()
        width = add_window.winfo_width()
        height = add_window.winfo_height()
        x = (add_window.winfo_screenwidth() // 2) - (width // 2)
        y = (add_window.winfo_screenheight() // 2) - (height // 2)
        add_window.geometry(f"{width}x{height}+{x}+{y}")
        
        # Frame
        frame = ttk.Frame(add_window, padding="20")
        frame.pack(fill=tk.BOTH, expand=True)
        
        # Eingabefelder
        ttk.Label(frame, text="Programmname:").grid(row=0, column=0, sticky="w", pady=5)
        name_entry = ttk.Entry(frame, width=30)
        name_entry.grid(row=0, column=1, columnspan=2, sticky="we", pady=5)
        
        ttk.Label(frame, text="Programmpfad:").grid(row=1, column=0, sticky="w", pady=5)
        path_entry = ttk.Entry(frame, width=30)
        path_entry.grid(row=1, column=1, sticky="we", pady=5)
        
        browse_button = ttk.Button(frame, text="Durchsuchen", 
                                  command=lambda: self.browse_file_and_clear(path_entry))
        browse_button.grid(row=1, column=2, padx=5, pady=5)
        
        ttk.Label(frame, text="Kategorie:").grid(row=2, column=0, sticky="w", pady=5)
        
        # Combobox für Kategorien
        category_combobox = ttk.Combobox(frame, width=28, values=sorted(self.categories))
        category_combobox.grid(row=2, column=1, columnspan=2, sticky="we", pady=5)
        if self.categories:
            category_combobox.current(0)  # Erste Kategorie auswählen, falls vorhanden
        
        # Checkbox für Adminrechte
        admin_var = tk.BooleanVar(value=False)
        admin_check = ttk.Checkbutton(frame, text="Benötigt Administratorrechte", variable=admin_var)
        admin_check.grid(row=3, column=0, columnspan=3, sticky="w", pady=5)
        
        # Buttons
        button_frame = ttk.Frame(frame)
        button_frame.grid(row=4, column=0, columnspan=3, pady=15)
        
        save_button = ttk.Button(button_frame, text="Speichern", 
                                command=lambda: self.save_new_program(
                                    name_entry.get(), 
                                    path_entry.get(), 
                                    category_combobox.get(),
                                    admin_var.get(),
                                    add_window))
        save_button.pack(side=tk.LEFT, padx=5)
        
        cancel_button = ttk.Button(button_frame, text="Abbrechen", command=add_window.destroy)
        cancel_button.pack(side=tk.LEFT, padx=5)
        
        # Focus auf erstes Eingabefeld
        name_entry.focus_set()
    
    def browse_file_and_clear(self, entry_widget):
        """Durchsucht nach einer Datei und löscht vorher den Inhalt des Eingabefelds"""
        entry_widget.delete(0, tk.END)
        filename = filedialog.askopenfilename(filetypes=[("Ausführbare Dateien", "*.exe"), ("Alle Dateien", "*.*")])
        if filename:
            entry_widget.insert(0, filename)
    
    def save_new_program(self, name, path, category, requires_admin, window):
        if not name or not path:
            messagebox.showwarning("Warnung", "Bitte füllen Sie alle Felder aus.")
            return
        
        if not os.path.exists(path):
            messagebox.showwarning("Warnung", "Der angegebene Pfad existiert nicht.")
            return
        
        # Neues Programm hinzufügen (nur mit Kategorie, wenn eine ausgewählt wurde)
        new_program = {"Name": name, "Pfad": path}
        if category:
            new_program["Kategorie"] = category
            
            # Füge Kategorie hinzu, falls sie noch nicht existiert
            if category not in self.categories:
                self.categories.append(category)
                
                # Aktualisiere die Kategorie-Listbox
                self.category_listbox.delete(1, tk.END)  # Lösche alle außer "Alle"
                for cat in sorted(self.categories):
                    self.category_listbox.insert(tk.END, cat)
        
        # Adminrechte-Flag hinzufügen
        if requires_admin:
            new_program["Adminrechte"] = True
        
        self.programs.append(new_program)
        self.save_data()
        self.populate_program_list()
        window.destroy()
    
    def remove_program(self):
        selected_items = self.program_tree.selection()
        if not selected_items:
            messagebox.showinfo("Information", "Bitte wählen Sie ein Programm zum Entfernen aus.")
            return
        
        item_id = selected_items[0]
        program_values = self.program_tree.item(item_id, "values")
        program_name = program_values[0].strip()  # Leerzeichen am Anfang entfernen
        
        # Bestätigung
        confirm = messagebox.askyesno("Bestätigung", f"Möchten Sie '{program_name}' wirklich entfernen?")
        if not confirm:
            return
        
        # Finde den Index des zu entfernenden Programms
        found = False
        for i, program in enumerate(self.programs):
            if program["Name"] == program_name:
                del self.programs[i]
                found = True
                break
        
        # Wenn kein exakter Treffer gefunden wurde, versuche es ohne Leerzeichen
        if not found:
            for i, program in enumerate(self.programs):
                if program["Name"].strip() == program_name:
                    del self.programs[i]
                    break
        
        self.save_data()
        self.populate_program_list()
    
    def edit_program(self):
        selected_items = self.program_tree.selection()
        if not selected_items:
            messagebox.showinfo("Information", "Bitte wählen Sie ein Programm zum Bearbeiten aus.")
            return
        
        item_id = selected_items[0]
        program_values = self.program_tree.item(item_id, "values")
        program_name = program_values[0].strip()  # Leerzeichen am Anfang entfernen
        program_category = program_values[1] if len(program_values) > 1 else ""
        program_path = self.program_tree.item(item_id, "tags")[0]
        
        # Finde den Index des zu bearbeitenden Programms
        program_index = None
        for i, program in enumerate(self.programs):
            if program["Name"] == program_name.strip():  # Vergleich ohne führende Leerzeichen
                program_index = i
                break
        
        if program_index is None:
            # Wenn kein exakter Treffer gefunden wird, versuche ohne Leerzeichen zu vergleichen
            for i, program in enumerate(self.programs):
                if program["Name"].strip() == program_name.strip():  # Beide Namen ohne Leerzeichen vergleichen
                    program_index = i
                    break
        
        if program_index is None:
            messagebox.showinfo("Information", f"Programm '{program_name}' konnte nicht gefunden werden.")
            return
        
        # Bearbeitungsfenster
        edit_window = tk.Toplevel(self.root)
        edit_window.title("Programm bearbeiten")
        edit_window.geometry("450x280")  # Etwas mehr Platz für die Checkbox
        edit_window.resizable(False, False)
        edit_window.grab_set()  # Modal machen
        
        # Zentriere das Dialogfenster
        edit_window.update_idletasks()
        width = edit_window.winfo_width()
        height = edit_window.winfo_height()
        x = (edit_window.winfo_screenwidth() // 2) - (width // 2)
        y = (edit_window.winfo_screenheight() // 2) - (height // 2)
        edit_window.geometry(f"{width}x{height}+{x}+{y}")
        
        # Frame
        frame = ttk.Frame(edit_window, padding="20")
        frame.pack(fill=tk.BOTH, expand=True)
        
        # Eingabefelder
        ttk.Label(frame, text="Programmname:").grid(row=0, column=0, sticky="w", pady=5)
        name_entry = ttk.Entry(frame, width=30)
        name_entry.insert(0, program_name.strip())  # Original-Programmnamen ohne Leerzeichen einfügen
        name_entry.grid(row=0, column=1, columnspan=2, sticky="we", pady=5)
        
        ttk.Label(frame, text="Programmpfad:").grid(row=1, column=0, sticky="w", pady=5)
        path_entry = ttk.Entry(frame, width=30)
        path_entry.insert(0, program_path)
        path_entry.grid(row=1, column=1, sticky="we", pady=5)
        
        browse_button = ttk.Button(frame, text="Durchsuchen", 
                                  command=lambda: self.browse_file(path_entry))
        browse_button.grid(row=1, column=2, padx=5, pady=5)
        
        ttk.Label(frame, text="Kategorie:").grid(row=2, column=0, sticky="w", pady=5)
        
        # Combobox für Kategorien
        category_combobox = ttk.Combobox(frame, width=28, values=sorted(self.categories))
        category_combobox.grid(row=2, column=1, columnspan=2, sticky="we", pady=5)
        if program_category:
            category_combobox.set(program_category)
        elif self.categories:
            category_combobox.current(0)  # Erste Kategorie auswählen, falls vorhanden
            
        # Checkbox für Adminrechte
        admin_var = tk.BooleanVar(value=self.programs[program_index].get("Adminrechte", False))
        admin_check = ttk.Checkbutton(frame, text="Benötigt Administratorrechte", variable=admin_var)
        admin_check.grid(row=3, column=0, columnspan=3, sticky="w", pady=5)
        
        # Buttons
        button_frame = ttk.Frame(frame)
        button_frame.grid(row=4, column=0, columnspan=3, pady=15)
        
        save_button = ttk.Button(button_frame, text="Speichern", 
                               command=lambda: self.update_program(
                                   program_index, 
                                   name_entry.get(), 
                                   path_entry.get(), 
                                   category_combobox.get(),
                                   admin_var.get(),
                                   edit_window))
        save_button.pack(side=tk.LEFT, padx=5)
        
        cancel_button = ttk.Button(button_frame, text="Abbrechen", command=edit_window.destroy)
        cancel_button.pack(side=tk.LEFT, padx=5)
    
    def browse_file(self, entry_widget):
        filename = filedialog.askopenfilename(filetypes=[("Ausführbare Dateien", "*.exe"), ("Alle Dateien", "*.*")])
        if filename:
            entry_widget.delete(0, tk.END)
            entry_widget.insert(0, filename)
    
    def update_program(self, index, name, path, category, requires_admin, window):
        if not name or not path:
            messagebox.showwarning("Warnung", "Bitte füllen Sie alle Felder aus.")
            return
        
        if not os.path.exists(path):
            messagebox.showwarning("Warnung", "Der angegebene Pfad existiert nicht.")
            return
        
        # Programm aktualisieren (nur mit Kategorie, wenn eine ausgewählt wurde)
        updated_program = {"Name": name, "Pfad": path}
        if category:
            updated_program["Kategorie"] = category
            
            # Füge Kategorie hinzu, falls sie noch nicht existiert
            if category not in self.categories:
                self.categories.append(category)
                
                # Aktualisiere die Kategorie-Listbox
                self.category_listbox.delete(1, tk.END)  # Lösche alle außer "Alle"
                for cat in sorted(self.categories):
                    self.category_listbox.insert(tk.END, cat)
        
        # Adminrechte-Flag hinzufügen
        if requires_admin:
            updated_program["Adminrechte"] = True
        
        self.programs[index] = updated_program
        self.save_data()
        self.populate_program_list()
        window.destroy()
    
    def sort_treeview(self, column, reverse):
        """Sortiert die Treeview nach der angegebenen Spalte"""
        if self.sort_column == column:
            # Wenn dieselbe Spalte erneut geklickt wird, umkehren
            reverse = not self.sort_reverse
        
        # Speichern des aktuellen Sortierzustands
        self.sort_column = column
        self.sort_reverse = reverse
        
        # Aktualisiere die Programmliste mit dem neuen Sortierzustand
        self.populate_program_list()

if __name__ == "__main__":
    root = tk.Tk()
    app = ProgramLauncher(root)
root.mainloop()