# Main application file for MacroPad Server

import tkinter as tk
from tkinter import ttk, messagebox
import os
import sys
import threading
import socket
import qrcode
import webbrowser
import pystray
from PIL import Image, ImageTk, ImageDraw, ImageFont

from config import VERSION, DEFAULT_PORT, THEME
from macro_manager import MacroManager
from web_server import WebServer
from ui_components import MacroDialog, MacroSelector, TabManager


class MacroPadServer:
    def __init__(self, root):
        self.root = root
        self.root.title("MacroPad Server")
        self.root.geometry("800x600")
        self.configure_styles()
        
        # Set up directories
        if getattr(sys, 'frozen', False):
            base_dir = os.path.dirname(sys.executable)
        else:
            base_dir = os.path.dirname(os.path.abspath(__file__))
        
        data_file = os.path.join(base_dir, "macros.json")
        images_dir = os.path.join(base_dir, "macro_images")
        os.makedirs(images_dir, exist_ok=True)

        # Initialize components
        self.macro_manager = MacroManager(data_file, images_dir, base_dir)
        self.web_server = WebServer(self.macro_manager, base_dir, DEFAULT_PORT)
        
        # UI state
        self.current_sort = "name"
        self.current_tab = "All"
        self.image_cache = {}
        
        # Server state
        self.server_running = False
        self.flask_thread = None
        
        # Tray state
        self.tray_icon = None
        self.is_closing = False

        # Create UI
        self.create_ui()
        
        # Set up window event handlers
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.root.bind('<Unmap>', self.on_minimize)
        
        # Initialize tray icon
        self.create_tray_icon()

    def configure_styles(self):
        """Configure the dark theme styles"""
        self.root.configure(bg=THEME['bg_color'])

        style = ttk.Style()
        style.theme_use("clam")
        style.configure("TButton", background=THEME['button_bg'], foreground=THEME['button_fg'])
        style.map("TButton", background=[("active", THEME['accent_color'])])
        style.configure("TFrame", background=THEME['bg_color'])
        style.configure("TLabel", background=THEME['bg_color'], foreground=THEME['fg_color'])
        
        # Configure notebook (tabs) style
        style.configure("TNotebook", background=THEME['bg_color'], borderwidth=0)
        style.configure("TNotebook.Tab", background=THEME['tab_bg'], foreground=THEME['fg_color'], 
                       padding=[12, 8], borderwidth=0)
        style.map("TNotebook.Tab", 
         background=[("selected", THEME['tab_selected'])],
         foreground=[("selected", THEME['fg_color'])],
         padding=[("selected", [12, 8])])  # Keep same padding when selected

    def create_ui(self):
        """Create the main user interface"""
        # Create main container
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Left side: Macro list and controls
        left_frame = ttk.Frame(main_frame)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # Sort controls
        self._create_sort_controls(left_frame)
        
        # Create notebook for tabs
        self.notebook = ttk.Notebook(left_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
        self.notebook.bind("<<NotebookTabChanged>>", self.on_tab_change)

        # Button controls
        self._create_macro_buttons(left_frame)

        # Right side: Server controls
        right_frame = ttk.Frame(main_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(10, 0))

        self._create_server_controls(right_frame)
        
        # Version label
        version_label = tk.Label(self.root, text=VERSION, 
                            bg=THEME['bg_color'], fg=THEME['fg_color'], 
                            font=('Helvetica', 8))
        version_label.pack(side=tk.BOTTOM, anchor=tk.SE, padx=5, pady=(0, 2))

        # Initialize display
        self.setup_tabs()
        self.display_macros()

    def _create_sort_controls(self, parent):
        """Create sorting controls"""
        sort_frame = ttk.Frame(parent)
        sort_frame.pack(fill=tk.X, pady=(0, 10))
        
        ttk.Label(sort_frame, text="Sort by:").pack(side=tk.LEFT, padx=(0, 5))
        self.sort_var = tk.StringVar(value=self.current_sort)
        sort_combo = ttk.Combobox(sort_frame, textvariable=self.sort_var, 
                                  values=["name", "type", "recent"], 
                                  state="readonly", width=10)
        sort_combo.pack(side=tk.LEFT, padx=(0, 10))
        sort_combo.bind("<<ComboboxSelected>>", self.on_sort_change)
        
        # Tab management button
        ttk.Button(sort_frame, text="Manage Tabs", 
                  command=self.manage_tabs).pack(side=tk.RIGHT, padx=(5, 0))

    def _create_macro_buttons(self, parent):
        """Create macro management buttons"""
        button_frame = ttk.Frame(parent)
        button_frame.pack(fill=tk.X, pady=(0, 10))

        ttk.Button(button_frame, text="Add Macro", command=self.add_macro).pack(side=tk.LEFT, padx=2)
        ttk.Button(button_frame, text="Edit Macro", command=self.edit_macro).pack(side=tk.LEFT, padx=2)
        ttk.Button(button_frame, text="Delete Macro", command=self.delete_macro).pack(side=tk.LEFT, padx=2)

    def _create_server_controls(self, parent):
        """Create web server controls"""
        server_frame = ttk.Frame(parent)
        server_frame.pack(fill=tk.X, pady=10)

        ttk.Label(server_frame, text="Web Server Control:").grid(row=0, column=0, sticky="w", pady=5)

        self.server_button = ttk.Button(server_frame, text="Start Web Server", command=self.toggle_server)
        self.server_button.grid(row=0, column=1, padx=5, pady=5)

        # Status display
        self.status_var = tk.StringVar(value="Web server not running")
        ttk.Label(server_frame, textvariable=self.status_var).grid(row=1, column=0, columnspan=2, sticky="w", pady=5)

        # QR code display
        self.qr_label = ttk.Label(parent)
        self.qr_label.pack(pady=10)

        # URL display
        self.url_var = tk.StringVar(value="")
        self.url_label = ttk.Label(parent, textvariable=self.url_var)
        self.url_label.pack(pady=5)
        
        # Browser button
        self.browser_button = ttk.Button(parent, text="Open in Browser", 
                                         command=self.open_in_browser, state=tk.DISABLED)
        self.browser_button.pack(pady=5)

    def setup_tabs(self):
        """Initialize tabs based on macro categories"""
        # Clear existing tabs
        for tab in self.notebook.tabs():
            self.notebook.forget(tab)
        
        # Get unique tabs from macro manager
        tabs = self.macro_manager.get_unique_tabs()
        
        for tab_name in tabs:
            frame = ttk.Frame(self.notebook)
            self.notebook.add(frame, text=tab_name)

    def get_current_tab_name(self):
        """Get the name of the currently selected tab"""
        try:
            current_tab_id = self.notebook.select()
            return self.notebook.tab(current_tab_id, "text")
        except:
            return "All"

    def on_tab_change(self, event=None):
        """Handle tab change event"""
        self.current_tab = self.get_current_tab_name()
        self.display_macros()

    def on_sort_change(self, event=None):
        """Handle sort option change"""
        self.current_sort = self.sort_var.get()
        self.display_macros()

    def display_macros(self):
        """Display macros in the current tab"""
        # Get current tab frame
        try:
            current_tab_id = self.notebook.select()
            current_frame = self.notebook.nametowidget(current_tab_id)
        except:
            return

        # Clear previous content
        for widget in current_frame.winfo_children():
            widget.destroy()

        # Create scrollable canvas
        canvas = tk.Canvas(current_frame, bg=THEME['bg_color'], highlightthickness=0)
        scrollbar = ttk.Scrollbar(current_frame, orient="vertical", command=canvas.yview)
        scrollable_frame = ttk.Frame(canvas)

        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

        # Get sorted and filtered macros
        sorted_macros = self.macro_manager.get_sorted_macros(self.current_sort)
        filtered_macros = self.macro_manager.filter_macros_by_tab(sorted_macros, self.current_tab)

        # Display macros
        for macro_id, macro in filtered_macros:
            self._create_macro_button(scrollable_frame, macro_id, macro)

        # Display message if no macros
        if not filtered_macros:
            label = tk.Label(scrollable_frame, text="No macros in this category",
                          bg=THEME['bg_color'], fg=THEME['fg_color'])
            label.pack(pady=20)

    def _create_macro_button(self, parent, macro_id, macro):
        """Create a button for a single macro"""
        frame = ttk.Frame(parent)
        frame.pack(fill="x", pady=5, padx=5)
        
        button = tk.Button(
            frame, text=macro["name"], 
            bg=THEME['button_bg'], fg=THEME['button_fg'],
            activebackground=THEME['accent_color'], activeforeground=THEME['button_fg'],
            relief=tk.RAISED, bd=2, pady=8, 
            command=lambda: self.macro_manager.execute_macro(macro_id)
        )

        # Add image if available
        if "image_path" in macro and macro["image_path"]:
            try:
                if macro["image_path"] in self.image_cache:
                    button_image = self.image_cache[macro["image_path"]]
                else:
                    img_path = os.path.join(self.macro_manager.app_dir, macro["image_path"])
                    img = Image.open(img_path)
                    img = img.resize((32, 32))
                    button_image = ImageTk.PhotoImage(img)
                    self.image_cache[macro["image_path"]] = button_image
                
                button.config(image=button_image, compound=tk.LEFT)
                button.image = button_image  # Keep reference
            except Exception as e:
                print(f"Error loading image for {macro['name']}: {e}")

        button.pack(side=tk.LEFT, fill=tk.X, expand=True)

    def manage_tabs(self):
        """Open tab management dialog"""
        tab_manager = TabManager(self.root, self.macro_manager)
        tab_manager.show()
        self.setup_tabs()
        self.display_macros()

    def add_macro(self):
        """Add a new macro"""
        dialog = MacroDialog(self.root, self.macro_manager)
        result = dialog.show()
        if result:
            self.setup_tabs()
            self.display_macros()

    def edit_macro(self):
        """Edit an existing macro"""
        selector = MacroSelector(self.root, self.macro_manager, "Select Macro to Edit")
        macro_id = selector.show()
        if macro_id:
            dialog = MacroDialog(self.root, self.macro_manager, macro_id)
            result = dialog.show()
            if result:
                self.setup_tabs()
                self.display_macros()

    def delete_macro(self):
        """Delete a macro"""
        selector = MacroSelector(self.root, self.macro_manager, "Select Macro to Delete")
        macro_id = selector.show()
        if macro_id:
            macro_name = self.macro_manager.macros[macro_id]["name"]
            if messagebox.askyesno("Confirm Deletion", f"Are you sure you want to delete macro '{macro_name}'?"):
                self.macro_manager.delete_macro(macro_id)
                self.setup_tabs()
                self.display_macros()

    def toggle_server(self):
        """Toggle web server on/off"""
        if self.server_running:
            self.stop_server()
        else:
            self.start_server()

    def start_server(self):
        """Start the web server"""
        try:
            if not self.server_running:
                self.server_running = True
                self.flask_thread = threading.Thread(target=self.run_web_server)
                self.flask_thread.daemon = True
                self.flask_thread.start()
                self.server_button.config(text="Stop Web Server")
                
                # Get IP address and display info
                ip_address = self.get_ip_address()
                if ip_address:
                    url = f"http://{ip_address}:{DEFAULT_PORT}"
                    url_text = f"Web UI available at:\n{url}"
                    self.url_var.set(url_text)
                    self.browser_button.config(state=tk.NORMAL)
                    self.generate_qr_code(url)
                else:
                    self.url_var.set("No network interfaces found")
        except Exception as e:
            self.status_var.set(f"Error starting server: {e}")
            self.server_running = False

    def stop_server(self):
        """Stop the web server"""
        if self.server_running:
            self.server_running = False
            self.status_var.set("Web server stopped")
            self.server_button.config(text="Start Web Server")
            self.url_var.set("")
            self.browser_button.config(state=tk.DISABLED)
            self.qr_label.config(image="")

    def run_web_server(self):
        """Run the web server in a separate thread"""
        self.status_var.set(f"Web server running on port {DEFAULT_PORT}")
        try:
            self.web_server.run()
        except Exception as e:
            self.status_var.set(f"Web server error: {e}")
            self.server_running = False

    def get_ip_address(self):
        """Get the primary internal IPv4 address"""
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            ip = s.getsockname()[0]
            s.close()
            return ip
        except Exception as e:
            print(f"Error getting IP address: {e}")
            try:
                hostname = socket.gethostname()
                ip = socket.gethostbyname(hostname)
                if not ip.startswith("127."):
                    return ip
                
                for addr_info in socket.getaddrinfo(hostname, None):
                    potential_ip = addr_info[4][0]
                    if '.' in potential_ip and not potential_ip.startswith("127."):
                        return potential_ip
            except:
                pass
            return "127.0.0.1"

    def generate_qr_code(self, url):
        """Generate and display QR code for the URL"""
        try:
            qr = qrcode.QRCode(
                version=1,
                error_correction=qrcode.constants.ERROR_CORRECT_L,
                box_size=10,
                border=4,
            )
            qr.add_data(url)
            qr.make(fit=True)
            
            qr_img = qr.make_image(fill_color="black", back_color="white")
            qr_photoimg = ImageTk.PhotoImage(qr_img)
            
            self.qr_label.config(image=qr_photoimg)
            self.qr_label.image = qr_photoimg
        except ImportError:
            self.qr_label.config(text="QR code generation requires 'qrcode' package")
        except Exception as e:
            print(f"Error generating QR code: {e}")
            self.qr_label.config(text="Error generating QR code")

    def open_in_browser(self):
        """Open the web interface in browser"""
        if self.server_running:
            webbrowser.open(f"http://localhost:{DEFAULT_PORT}")

    def on_minimize(self, event):
        """Handle window minimize event"""
        # Only minimize to tray if the window is being iconified, not just unmapped
        if event.widget == self.root and self.root.state() == 'iconic':
            self.root.withdraw()  # Hide window

    def create_tray_icon(self):
        """Create system tray icon"""
        try:
            # Create a simple icon image with M letter
            icon_image = Image.new("RGB", (64, 64), THEME['accent_color'])
            draw = ImageDraw.Draw(icon_image)
            
            try:
                # Try to use a system font
                font = ImageFont.truetype("arial.ttf", 40)
            except:
                try:
                    # Try other common fonts
                    font = ImageFont.truetype("calibri.ttf", 40)
                except:
                    # Fall back to default font
                    font = ImageFont.load_default()
            
            # Draw "MP" in the center
            text = "MP"
            bbox = draw.textbbox((0, 0), text, font=font)
            text_width = bbox[2] - bbox[0]
            text_height = bbox[3] - bbox[1]
            x = (64 - text_width) // 2
            y = (64 - text_height) // 2
            draw.text((x, y), text, fill="white", font=font)
            
            menu = (
                pystray.MenuItem('Show', self.show_window),
                pystray.MenuItem('Exit', self.exit_app)
            )
            
            self.tray_icon = pystray.Icon("macropad", icon_image, "MacroPad Server", menu)
            
            # Run tray icon in a separate thread
            tray_thread = threading.Thread(target=self.tray_icon.run, daemon=True)
            tray_thread.start()
            
        except Exception as e:
            print(f"Error creating tray icon: {e}")
            # Tray icon is optional, continue without it

    def show_window(self, icon=None, item=None):
        """Show window from tray"""
        self.root.deiconify()
        self.root.state('normal')
        self.root.lift()
        self.root.focus_force()

    def exit_app(self, icon=None, item=None):
        """Exit the application"""
        self.is_closing = True
        self.stop_server()
        
        if self.tray_icon:
            try:
                self.tray_icon.stop()
            except:
                pass
                
        try:
            self.root.quit()
        except:
            pass
        
        # Force exit if needed
        import os
        os._exit(0)

    def on_closing(self):
        """Handle window close event - exit the application"""
        self.exit_app()


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