#!/usr/bin/env python3
"""
Reusable UI Components for SDL2-based applications
Provides common graphics operations and UI elements
"""

import os
import time
from typing import Dict, List, Optional, Tuple, Any
import sdl2
import sdl2.ext


class UIColors:
    """Standard color palette for UI components"""
    
    def __init__(self):
        self.colors = {
            'white': sdl2.ext.Color(255, 255, 255),
            'black': sdl2.ext.Color(0, 0, 0),
            'gray': sdl2.ext.Color(128, 128, 128),
            'light_gray': sdl2.ext.Color(200, 200, 200),
            'dark_gray': sdl2.ext.Color(64, 64, 64),
            'blue': sdl2.ext.Color(70, 130, 200),
            'light_blue': sdl2.ext.Color(120, 180, 255),
            'green': sdl2.ext.Color(50, 200, 50),
            'light_green': sdl2.ext.Color(100, 255, 100),
            'red': sdl2.ext.Color(200, 50, 50),
            'yellow': sdl2.ext.Color(255, 220, 0),
            'orange': sdl2.ext.Color(255, 165, 0),
            'purple': sdl2.ext.Color(150, 50, 200)
        }
    
    def get(self, color_name: str):
        """Get color by name"""
        return self.colors.get(color_name, self.colors['white'])


class FontManager:
    """Manages multiple font sizes for the application"""
    
    def __init__(self, font_path: Optional[str] = None):
        self.font_path = font_path or self._find_default_font()
        self.fonts = {}
        self._initialize_fonts()
    
    def _find_default_font(self) -> Optional[str]:
        """Find a suitable default font"""
        font_paths = [
            "assets/decterm.ttf",
            "./assets/terminal.ttf", 
            "./assets/AmaticSC-Regular.ttf"
        ]
        
        for path in font_paths:
            if os.path.exists(path):
                return path
        return None
    
    def _initialize_fonts(self):
        """Initialize font managers for different sizes"""
        sizes = {
            'title': 36,
            'large': 28, 
            'medium': 22,
            'small': 18,
            'tiny': 14
        }
        
        for name, size in sizes.items():
            self.fonts[name] = sdl2.ext.FontManager(
                font_path=self.font_path, 
                size=size
            )
    
    def get(self, size: str):
        """Get font manager by size name"""
        return self.fonts.get(size, self.fonts['medium'])


class UIRenderer:
    """Main UI rendering class with reusable drawing operations"""
    
    def __init__(self, renderer: sdl2.ext.Renderer, window_size: Tuple[int, int] = (640, 480)):
        self.renderer = renderer
        self.window_width, self.window_height = window_size
        self.colors = UIColors()
        self.fonts = FontManager()
        self.sprite_factory = sdl2.ext.SpriteFactory(renderer=renderer)
    
    def clear_screen(self, color_name: str = 'black'):
        """Clear screen with specified color"""
        self.renderer.clear(self.colors.get(color_name))
    
    def present(self):
        """Present the rendered frame"""
        self.renderer.present()
    
    def draw_background_pattern(self, pattern_type: str = 'gradient'):
        """Draw subtle background patterns"""
        if pattern_type == 'gradient':
            for y in range(0, self.window_height, 20):
                alpha = int(20 * (1 - y / self.window_height))
                if alpha > 5:
                    color = sdl2.ext.Color(alpha, alpha, alpha * 2)
                    self.renderer.draw_line((0, y, self.window_width, y), color)
    
    def draw_text(self, text: str, x: int, y: int, color_name: str = 'white', 
                  font_size: str = 'medium', center: bool = False) -> Tuple[int, int]:
        """Draw text on screen with improved styling"""
        if not text:
            return (0, 0)
        
        color = self.colors.get(color_name)
        font = self.fonts.get(font_size)
        text_sprite = self.sprite_factory.from_text(text, color=color, fontmanager=font)
        
        if center:
            x = x - text_sprite.size[0] // 2
        
        text_sprite.position = (x, y)
        self.renderer.copy(text_sprite, dstrect=text_sprite.position)
        return text_sprite.size
    
    def draw_panel(self, x: int, y: int, width: int, height: int, 
                   bg_color: str = 'dark_gray', border_color: str = 'gray', 
                   border_width: int = 2):
        """Draw a styled panel/box"""
        bg = self.colors.get(bg_color)
        border = self.colors.get(border_color)
        
        # Fill background
        self.renderer.fill((x, y, width, height), bg)
        
        # Draw border
        for i in range(border_width):
            self.renderer.draw_rect((x + i, y + i, width - 2*i, height - 2*i), border)
    
    def draw_button(self, text: str, x: int, y: int, width: int, height: int, 
                    selected: bool = False, disabled: bool = False, 
                    font_size: str = 'medium'):
        """Draw a styled button"""
        # Button colors based on state
        if disabled:
            bg_color = 'black'
            border_color = 'dark_gray'
            text_color = 'dark_gray'
        elif selected:
            bg_color = 'blue'
            border_color = 'light_blue'
            text_color = 'white'
        else:
            bg_color = 'dark_gray'
            border_color = 'gray'
            text_color = 'light_gray'
        
        # Draw button background and border
        self.draw_panel(x, y, width, height, bg_color, border_color)
        
        # Draw button text centered
        text_x = x + width // 2
        text_y = y + height // 2 - 12  # Approximate text height offset
        self.draw_text(text, text_x, text_y, text_color, font_size, center=True)
    
    def draw_header(self, title: str, subtitle: str = '', y_pos: int = 0, 
                    height: int = 80, bg_color: str = 'dark_gray'):
        """Draw a header section with title and optional subtitle"""
        self.renderer.fill((0, y_pos, self.window_width, height), self.colors.get(bg_color))
        
        title_y = y_pos + 15
        self.draw_text(title, self.window_width // 2, title_y, 'light_blue', 'title', center=True)
        
        if subtitle:
            subtitle_y = title_y + 35
            self.draw_text(subtitle, self.window_width // 2, subtitle_y, 'light_green', 'medium', center=True)
    
    def draw_footer_help(self, help_text: str, y_pos: int = None):
        """Draw footer with help text"""
        if y_pos is None:
            y_pos = self.window_height - 60
        
        self.draw_panel(10, y_pos, self.window_width - 20, 40, 'black', 'dark_gray')
        self.draw_text(help_text, self.window_width // 2, y_pos + 15, 
                      'light_gray', 'tiny', center=True)
    
    def draw_list_item(self, text: str, x: int, y: int, width: int, height: int,
                       selected: bool = False, secondary_text: str = '', 
                       indicator: str = ''):
        """Draw a list item with optional secondary text and indicator"""
        # Background
        bg_color = 'blue' if selected else 'dark_gray'
        border_color = 'light_blue' if selected else 'gray'
        text_color = 'white' if selected else 'light_gray'
        
        self.draw_panel(x, y, width, height, bg_color, border_color)
        
        # Main text
        self.draw_text(text, x + 10, y + 5, text_color, 'medium')
        
        # Secondary text
        if secondary_text:
            secondary_color = 'light_gray' if selected else 'gray'
            self.draw_text(secondary_text, x + 10, y + 22, secondary_color, 'tiny')
        
        # Indicator (like ★ for active item)
        if indicator:
            indicator_x = x + width - 30
            indicator_color = 'light_green' if selected else 'yellow'
            self.draw_text(indicator, indicator_x, y + height // 2 - 8, 
                          indicator_color, 'small', center=True)
    
    def draw_error_dialog(self, message: str, title: str = "ERROR"):
        """Draw an error dialog overlay"""
        # Semi-transparent overlay
        overlay_color = sdl2.ext.Color(0, 0, 0, 128)
        self.renderer.fill((0, 0, self.window_width, self.window_height), overlay_color)
        
        # Error dialog box
        dialog_width = 400
        dialog_height = 120
        dialog_x = (self.window_width - dialog_width) // 2
        dialog_y = (self.window_height - dialog_height) // 2
        
        # Dialog background with red border
        self.draw_panel(dialog_x, dialog_y, dialog_width, dialog_height, 
                       'black', 'red', border_width=4)
        
        # Error title
        self.draw_text(title, dialog_x + dialog_width // 2, dialog_y + 20, 
                      'red', 'large', center=True)
        
        # Error message
        self.draw_text(message, dialog_x + dialog_width // 2, dialog_y + 50, 
                      'white', 'medium', center=True)
        
        # Dismiss instruction
        self.draw_text("Press any key to continue...", 
                      dialog_x + dialog_width // 2, dialog_y + 80, 
                      'light_gray', 'small', center=True)
    
    def draw_virtual_keyboard(self, keyboard_layout: List[List[str]], 
                             cursor_x: int, cursor_y: int, 
                             start_x: int = 50, start_y: int = 150):
        """Draw a virtual keyboard interface"""
        key_width = 45
        key_height = 30
        
        for row_idx, row in enumerate(keyboard_layout):
            row_y = start_y + row_idx * (key_height + 5)
            
            # Special handling for bottom row (commands)
            if row_idx == len(keyboard_layout) - 1:
                key_widths = [80] * len(row)  # Wider keys for commands
                x_offset = start_x + 90  # Center the bottom row
            else:
                key_widths = [key_width] * len(row)
                x_offset = start_x
            
            current_x = x_offset
            for col_idx, char in enumerate(row):
                selected = (row_idx == cursor_y and col_idx == cursor_x)
                
                # Key styling
                if selected:
                    bg_color = 'blue'
                    border_color = 'light_blue'
                    text_color = 'white'
                else:
                    bg_color = 'dark_gray'
                    border_color = 'gray'
                    text_color = 'light_gray'
                
                self.draw_panel(current_x, row_y, key_widths[col_idx], key_height,
                               bg_color, border_color)
                
                # Key text
                display_char = self._format_keyboard_char(char)
                text_x = current_x + key_widths[col_idx] // 2
                text_y = row_y + 8
                self.draw_text(display_char, text_x, text_y, text_color, 'tiny', center=True)
                
                current_x += key_widths[col_idx] + 5
    
    def _format_keyboard_char(self, char: str) -> str:
        """Format keyboard character for display"""
        char_map = {
            '<DEL': 'DEL',
            'SPACE': 'SPC'
        }
        return char_map.get(char, char)
    
    def draw_input_field(self, text: str, x: int, y: int, width: int, height: int,
                        active: bool = False, placeholder: str = ''):
        """Draw an input text field"""
        # Field styling
        if active:
            bg_color = 'white'
            border_color = 'blue'
            text_color = 'black'
        else:
            bg_color = 'light_gray'
            border_color = 'gray'
            text_color = 'black' if text else 'gray'
        
        self.draw_panel(x, y, width, height, bg_color, border_color, 2)
        
        # Display text or placeholder
        display_text = text if text else placeholder
        self.draw_text(display_text, x + 10, y + 8, 
                      'black' if text else 'gray', 'medium')
    
    def draw_progress_bar(self, value: int, max_value: int, x: int, y: int,
                         width: int, height: int = 20, 
                         bg_color: str = 'dark_gray', fill_color: str = 'green'):
        """Draw a progress bar"""
        # Background
        self.draw_panel(x, y, width, height, bg_color, 'gray')
        
        # Fill
        if max_value > 0:
            fill_width = int(width * (value / max_value))
            if fill_width > 0:
                self.renderer.fill((x + 2, y + 2, fill_width - 4, height - 4), 
                                  self.colors.get(fill_color))
        
        # Value text
        text = f"{value}/{max_value}"
        text_x = x + width // 2
        text_y = y + 3
        self.draw_text(text, text_x, text_y, 'white', 'tiny', center=True)
    
    def draw_menu_navigation_arrows(self, selected_index: int, total_items: int,
                                   x: int, y: int):
        """Draw navigation arrows for selected menu items"""
        if selected_index > 0:
            self.draw_text("▲", x, y - 15, 'white', 'small', center=True)
        if selected_index < total_items - 1:
            self.draw_text("▼", x, y + 15, 'white', 'small', center=True)


class GamepadInputHandler:
    """Handle gamepad input with debouncing and mapping"""
    
    def __init__(self, debounce_delay: float = 0.15):
        self.gamepad = None
        self.button_states = {}
        self.last_button_time = {}
        self.debounce_delay = debounce_delay
        self.init_gamepad()
    
    def init_gamepad(self):
        """Initialize gamepad support"""
        sdl2.SDL_Init(sdl2.SDL_INIT_JOYSTICK | sdl2.SDL_INIT_GAMECONTROLLER)
        
        num_joysticks = sdl2.SDL_NumJoysticks()
        if num_joysticks > 0:
            self.gamepad = sdl2.SDL_JoystickOpen(0)
            if self.gamepad:
                print(f"Gamepad detected: {sdl2.SDL_JoystickName(self.gamepad).decode()}")
        else:
            print("No gamepad detected - using keyboard fallback")
    
    def can_process_input(self, input_key: str, current_time: float) -> bool:
        """Check if enough time has passed to process input (debouncing)"""
        if input_key not in self.last_button_time:
            self.last_button_time[input_key] = current_time
            return True
        
        if current_time - self.last_button_time[input_key] > self.debounce_delay:
            self.last_button_time[input_key] = current_time
            return True
        
        return False
    
    def get_gamepad_input(self) -> Dict[str, bool]:
        """Get current gamepad input state"""
        if not self.gamepad:
            return {}
        
        current_time = time.time()
        inputs = {}
        
        # D-pad navigation
        hat_state = sdl2.SDL_JoystickGetHat(self.gamepad, 0)
        
        if hat_state & sdl2.SDL_HAT_UP and self.can_process_input('up', current_time):
            inputs['up'] = True
        if hat_state & sdl2.SDL_HAT_DOWN and self.can_process_input('down', current_time):
            inputs['down'] = True
        if hat_state & sdl2.SDL_HAT_LEFT and self.can_process_input('left', current_time):
            inputs['left'] = True
        if hat_state & sdl2.SDL_HAT_RIGHT and self.can_process_input('right', current_time):
            inputs['right'] = True
        
        # Buttons
        button_count = sdl2.SDL_JoystickNumButtons(self.gamepad)
        for i in range(min(button_count, 16)):
            if sdl2.SDL_JoystickGetButton(self.gamepad, i):
                button_key = f'button_{i}'
                if self.can_process_input(button_key, current_time):
                    inputs[button_key] = True
        
        return inputs
    
    def cleanup(self):
        """Cleanup gamepad resources"""
        if self.gamepad:
            sdl2.SDL_JoystickClose(self.gamepad)
            self.gamepad = None


class ScreenManager:
    """Manage different screens/states in the application"""
    
    def __init__(self):
        self.current_screen = "main_menu"
        self.screen_stack = []
        self.selected_index = 0
        self.screen_data = {}
    
    def push_screen(self, screen_name: str, data: Dict[str, Any] = None):
        """Push a new screen onto the stack"""
        self.screen_stack.append({
            'screen': self.current_screen,
            'index': self.selected_index,
            'data': self.screen_data.copy()
        })
        self.current_screen = screen_name
        self.selected_index = 0
        self.screen_data = data or {}
    
    def pop_screen(self):
        """Pop the previous screen from the stack"""
        if self.screen_stack:
            previous = self.screen_stack.pop()
            self.current_screen = previous['screen']
            self.selected_index = previous['index']
            self.screen_data = previous['data']
    
    def set_screen(self, screen_name: str, data: Dict[str, Any] = None):
        """Set current screen without using stack"""
        self.current_screen = screen_name
        self.selected_index = 0
        self.screen_data = data or {}
