import tkinter as tk
from tkinter import colorchooser
from PIL import ImageDraw
from typing import Callable, Optional
import sys
import os

# Add parent directory to path for imports (since we're in utils/ subdirectory)
_parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _parent_dir not in sys.path:
    sys.path.insert(0, _parent_dir)


class BrushHandler:
    """Handler for brush functionality in the GUI.
    
    This class encapsulates all brush operations including drawing,
    color selection, size adjustment, and mode toggling.
    """
    
    def __init__(self,
                 canvas: tk.Canvas,
                 get_current_image: Callable,
                 get_canvas_dimensions: Callable,
                 render_image: Callable,
                 root_window: tk.Tk,
                 area_selection_handler=None):
        """Initialize the brush handler.
        
        Args:
            canvas: The tkinter Canvas widget
            get_current_image: Function that returns the current ImageContainer
            get_canvas_dimensions: Function that returns (width, height) tuple
            render_image: Function to re-render the current image
            root_window: The root tkinter window
            area_selection_handler: Optional AreaSelectionHandler to disable when brush is active
        """
        self._canvas = canvas
        self._get_current_image = get_current_image
        self._get_canvas_dimensions = get_canvas_dimensions
        self._render_image = render_image
        self._root = root_window
        self._area_selection_handler = area_selection_handler
        
        # Brush state
        self._brushMode = False
        self._brushColor = "#000000"  # Default black
        self._brushSize = 5  # Default brush size
        self._lastBrushX = None
        self._lastBrushY = None
        self._isDrawing = False
        self._snapshotTaken = False  # Track if snapshot was taken for current stroke
        
        # UI element references (will be set by GUI)
        self._colorPreview = None
        self._brushStatusLabel = None
    
    def create_ui_panel(self, parent_frame: tk.Frame) -> None:
        """Create the brush settings UI panel.
        
        Args:
            parent_frame: The parent frame to pack the UI into
        """
        # Brush settings title
        tk.Label(parent_frame, text="Brush Settings", font=("Arial", 12, "bold"), bg="lightgray").pack(pady=15)
        
        # Color selection section
        color_section = tk.Frame(parent_frame, bg="lightgray")
        color_section.pack(pady=10, padx=15, fill="x")
        
        tk.Label(color_section, text="Brush Color:", font=("Arial", 10), bg="lightgray").pack(anchor="w")
        
        color_frame = tk.Frame(color_section, bg="lightgray")
        color_frame.pack(pady=5)
        
        # Color preview
        self._colorPreview = tk.Label(color_frame, bg=self._brushColor, width=15, height=3, relief="sunken", bd=2)
        self._colorPreview.pack(side="left", padx=5)
        
        def choose_color():
            color = colorchooser.askcolor(title="Choose Brush Color", color=self._brushColor)
            if color[1]:  # color[1] is the hex string
                self._brushColor = color[1]
                self._colorPreview.config(bg=self._brushColor)
        
        tk.Button(color_frame, text="Choose Color", command=choose_color, width=12).pack(side="left", padx=5)
        
        # Size selection section
        size_section = tk.Frame(parent_frame, bg="lightgray")
        size_section.pack(pady=10, padx=15, fill="x")
        
        tk.Label(size_section, text="Brush Size:", font=("Arial", 10), bg="lightgray").pack(anchor="w")
        
        size_frame = tk.Frame(size_section, bg="lightgray")
        size_frame.pack(pady=5, fill="x")
        
        self._sizeVar = tk.IntVar(value=self._brushSize)
        self._sizeScale = tk.Scale(size_frame, from_=1, to=50, orient="horizontal", 
                                   variable=self._sizeVar, length=200, bg="lightgray")
        self._sizeScale.pack(side="left", padx=5)
        
        self._sizeLabel = tk.Label(size_frame, textvariable=self._sizeVar, width=3, bg="lightgray")
        self._sizeLabel.pack(side="left", padx=5)
        
        def update_size(value):
            self._brushSize = self._sizeVar.get()
            self._sizeLabel.config(text=str(self._brushSize))
        
        self._sizeScale.config(command=update_size)
        
        # Current brush info
        info_frame = tk.Frame(parent_frame, bg="lightgray")
        info_frame.pack(pady=20, padx=15, fill="x")
        
        tk.Label(info_frame, text="Brush Status:", font=("Arial", 10, "bold"), bg="lightgray").pack(anchor="w")
        self._brushStatusLabel = tk.Label(info_frame, text="Inactive", fg="red", bg="lightgray", font=("Arial", 9))
        self._brushStatusLabel.pack(anchor="w", pady=5)
    
    def toggle_brush_mode(self) -> None:
        """Toggle brush mode on/off."""
        self._brushMode = not self._brushMode
        if self._brushMode:
            # Disable selection mode when brush mode is enabled
            if self._area_selection_handler and self._area_selection_handler.selection_mode:
                self._area_selection_handler.toggle_selection_mode()
            self._root.config(cursor="pencil")
            # Update status label
            if self._brushStatusLabel:
                self._brushStatusLabel.config(text="Active", fg="green")
        else:
            self._root.config(cursor="")
            self._isDrawing = False
            self._lastBrushX = None
            self._lastBrushY = None
            # Update status label
            if self._brushStatusLabel:
                self._brushStatusLabel.config(text="Inactive", fg="red")
    
    def on_mouse_click(self, event) -> bool:
        """Handle mouse click for brush.
        
        Returns:
            True if the click was handled by brush, False otherwise
        """
        if not self._brushMode:
            return False
        
        current_image = self._get_current_image()
        if current_image is None:
            return False
        
        self._isDrawing = True
        self._lastBrushX = event.x
        self._lastBrushY = event.y
        # Take snapshot for undo (only once per stroke)
        if not self._snapshotTaken:
            current_image.snapshot()
            self._snapshotTaken = True
        # Draw first point
        self._draw_brush_point(event.x, event.y)
        return True
    
    def on_mouse_drag(self, event) -> bool:
        """Handle mouse drag for brush.
        
        Returns:
            True if the drag was handled by brush, False otherwise
        """
        if not self._brushMode or not self._isDrawing:
            return False
        
        self._draw_brush_line(self._lastBrushX, self._lastBrushY, event.x, event.y)
        self._lastBrushX = event.x
        self._lastBrushY = event.y
        return True
    
    def on_mouse_release(self, event) -> bool:
        """Handle mouse release to stop brush.
        
        Returns:
            True if the release was handled by brush, False otherwise
        """
        if not self._brushMode or not self._isDrawing:
            return False
        
        self._isDrawing = False
        self._lastBrushX = None
        self._lastBrushY = None
        self._snapshotTaken = False  # Reset for next stroke
        return True
    
    def _draw_brush_point(self, x: int, y: int) -> None:
        """Draw a single brush point at the given canvas coordinates."""
        current_image = self._get_current_image()
        if current_image is None:
            return
        
        # Convert widget coordinates to canvas coordinates (accounts for borders/scroll)
        canvas_x = self._canvas.canvasx(x)
        canvas_y = self._canvas.canvasy(y)
        
        # Get image dimensions
        pil_image = current_image.getImage()
        img_width, img_height = pil_image.size
        
        # Get canvas dimensions
        canvas_width, canvas_height = self._get_canvas_dimensions()
        
        # Convert canvas coordinates to image coordinates
        # Since image is placed at (0,0) with anchor="nw", coordinates align directly
        img_x = int(canvas_x)
        img_y = int(canvas_y)
        
        # Ensure coordinates are within bounds
        img_x = max(0, min(img_x, img_width - 1))
        img_y = max(0, min(img_y, img_height - 1))
        
        # Convert hex color to RGB tuple
        hex_color = self._brushColor.lstrip('#')
        rgb_color = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
        
        # Draw on the image
        draw = ImageDraw.Draw(pil_image)
        # Draw an ellipse (circle) for the brush point
        radius = self._brushSize // 2
        draw.ellipse([img_x - radius, img_y - radius, img_x + radius, img_y + radius], 
                    fill=rgb_color, outline=rgb_color)
        
        # Update the image
        current_image._imageData = pil_image
        
        # Re-render
        self._render_image()
    
    def _draw_brush_line(self, x1: int, y1: int, x2: int, y2: int) -> None:
        """Draw a brush line between two canvas coordinates."""
        current_image = self._get_current_image()
        if current_image is None:
            return
        
        # Convert widget coordinates to canvas coordinates (accounts for borders/scroll)
        canvas_x1 = self._canvas.canvasx(x1)
        canvas_y1 = self._canvas.canvasy(y1)
        canvas_x2 = self._canvas.canvasx(x2)
        canvas_y2 = self._canvas.canvasy(y2)
        
        # Get image dimensions
        pil_image = current_image.getImage()
        img_width, img_height = pil_image.size
        
        # Get canvas dimensions
        canvas_width, canvas_height = self._get_canvas_dimensions()
        
        # Convert canvas coordinates to image coordinates
        # Since image is placed at (0,0) with anchor="nw", coordinates align directly
        img_x1 = int(canvas_x1)
        img_y1 = int(canvas_y1)
        img_x2 = int(canvas_x2)
        img_y2 = int(canvas_y2)
        
        # Ensure coordinates are within bounds
        img_x1 = max(0, min(img_x1, img_width - 1))
        img_y1 = max(0, min(img_y1, img_height - 1))
        img_x2 = max(0, min(img_x2, img_width - 1))
        img_y2 = max(0, min(img_y2, img_height - 1))
        
        # Convert hex color to RGB tuple
        hex_color = self._brushColor.lstrip('#')
        rgb_color = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
        
        # Draw on the image
        draw = ImageDraw.Draw(pil_image)
        # Draw a line with rounded ends
        radius = self._brushSize // 2
        
        # Draw the line itself
        draw.line([(img_x1, img_y1), (img_x2, img_y2)], fill=rgb_color, width=self._brushSize)
        
        # Draw rounded ends (circles at start and end)
        draw.ellipse([img_x1 - radius, img_y1 - radius, img_x1 + radius, img_y1 + radius], 
                    fill=rgb_color)
        draw.ellipse([img_x2 - radius, img_y2 - radius, img_x2 + radius, img_y2 + radius], 
                    fill=rgb_color)
        
        # Update the image
        current_image._imageData = pil_image
        
        # Re-render
        self._render_image()
    
    @property
    def brush_mode(self) -> bool:
        """Get the current brush mode state."""
        return self._brushMode

