import tkinter as tk
from tkinter import filedialog, messagebox, colorchooser, simpledialog
from PIL import ImageTk, ImageDraw, Image
from ImageContainer import ImageContainer
from ImageManipulation.ManipulationList import *
from SelectionArea import SelectionArea
from utils.area_selection import AreaSelectionHandler
from utils.brush import BrushHandler
from utils.text_entry import TextEntryHandler
from functools import partial


class GUI:
    """The GUI class responsible for the main application GUI.

    @warning This class is a singleton.
    """
    _instance = None

    # If the GUI has been initialised.
    _isInitialised = False

    _currentImage = None

    # Area selection handler (will be initialized in initialise())
    _areaSelectionHandler = None
    
    # Brush handler (will be initialized in initialise())
    _brushHandler = None
    
    # Text entry handler (will be initialized in initialise())
    _textEntryHandler = None
    
    _canvasImageWidth = None  # Store canvas image dimensions
    _canvasImageHeight = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(GUI, cls).__new__(cls)
        return cls._instance

    def initialise(self):
        """Initialise the GUI."""

        if self._isInitialised:
            return
        else:
            self._isInitialised = True

        # Main window
        root = tk.Tk()
        root.title("Image Viewer")
        root.geometry("1100x700")
        root.config(bg="white")
        icon = tk.PhotoImage(file='icon.png')
        root.tk.call('wm', 'iconphoto', root._w, icon)
        root.minsize(1100, 600)

        # Menus
        menu = tk.Menu(root)
        root.config(menu=menu)
        file_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="File", menu=file_menu)
        file_menu.add_command(label="Open Image", accelerator="Ctrl+O", command=lambda: self._openImage())
        file_menu.add_command(label="Open Camera", accelerator="Ctrl+K", command=lambda: self._openCamera())
        file_menu.add_command(label="Save Image", accelerator="Ctrl+S", command=lambda: self._saveImage())
        file_menu.add_command(label="Exit", accelerator="Ctrl+Q", command=root.quit)

        test_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Test", menu=test_menu)
        for manipulation in GetImageManipulationList():
            test_menu.add_command(
                label=manipulation.getManipulationName(),
                command=partial(self._applyManipulation, manipulation)
            )
        
        edit_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Edit", menu=edit_menu)
        edit_menu.add_command(label="Undo", accelerator="Ctrl+Z", command=lambda: self._undo())
        edit_menu.add_command(label="Redo", accelerator="Ctrl+Y", command=lambda: self._redo())
        edit_menu.add_separator()
        edit_menu.add_command(label="Toggle Selection Mode", accelerator="Ctrl+Shift+S", command=lambda: self._toggleSelectionMode())
        edit_menu.add_command(label="Toggle Selection Shape (Rect/Circle/Lasso)", accelerator="Ctrl+Shift+C", command=lambda: self._toggleSelectionShape())
        edit_menu.add_separator()
        edit_menu.add_command(label="Brush Tool", accelerator="Ctrl+B", command=lambda: self._toggleBrushMode())
        
        image_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Image", menu=image_menu)
        filter_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Filter", menu=filter_menu)
        clipboard_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Clipboard", menu=clipboard_menu)
        tools_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Tools", menu=tools_menu)
        shapes_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Shapes", menu=shapes_menu)
        colors_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Colors", menu=colors_menu)



        # Main container frame for image and settings
        main_container = tk.Frame(root, bg="white")
        main_container.pack(side="top", fill="both", expand=True, padx=10, pady=10)

        # Frame to hold image
        imgframe = tk.Frame(main_container, width=500, height=500, bg="lightgray", relief="sunken", bd=2)
        imgframe.pack(side="left", fill="both", expand=True, padx=(0, 10))

        # Canvas to display image and draw selection
        self._imageCanvas = tk.Canvas(imgframe, width=500, height=500, bg="white")
        self._imageCanvas.pack(expand=True, fill="both")
 
        # Label to hold the image (will be placed on canvas)
        self._imageLabel = tk.Label(self._imageCanvas, bg="white")
        
        # Bind mouse events for interactive selection and brush
        self._imageCanvas.bind("<Button-1>", self._onMouseClick)
        self._imageCanvas.bind("<B1-Motion>", self._onMouseDrag)
        self._imageCanvas.bind("<ButtonRelease-1>", self._onMouseRelease)
        self._imageCanvas.bind("<Button-3>", self._onRightClick)  # Right-click for context menu

        # Settings panel for brush controls
        settings_panel = tk.Frame(main_container, width=250, bg="lightgray", relief="sunken", bd=2)
        settings_panel.pack(side="right", fill="y", padx=(10, 0))
        settings_panel.pack_propagate(False)  # Prevent frame from shrinking

        self._root = root

        # Initialize selection area
        selection_area = SelectionArea()
        
        # Initialize area selection handler
        def get_current_image():
            return self._currentImage
        
        def get_canvas_dimensions():
            if self._canvasImageWidth and self._canvasImageHeight:
                return (self._canvasImageWidth, self._canvasImageHeight)
            else:
                canvas_width = self._imageCanvas.winfo_width()
                canvas_height = self._imageCanvas.winfo_height()
                return (canvas_width, canvas_height)
        
        # Area selection handler from utils/area_selection.py
        self._areaSelectionHandler = AreaSelectionHandler(
            canvas=self._imageCanvas,
            get_current_image=get_current_image,
            get_canvas_dimensions=get_canvas_dimensions,
            render_image=self._renderCurrentImage,
            root_window=root,
            selection_area=selection_area
        )
        
        # Brush handler from utils/brush.py
        self._brushHandler = BrushHandler(
            canvas=self._imageCanvas,
            get_current_image=get_current_image,
            get_canvas_dimensions=get_canvas_dimensions,
            render_image=self._renderCurrentImage,
            root_window=root,
            area_selection_handler=self._areaSelectionHandler
        )
        
        # Create brush UI panel
        self._brushHandler.create_ui_panel(settings_panel)
        
        # Text entry handler from utils/text_entry.py
        self._textEntryHandler = TextEntryHandler(root)

        # Key bindings
        root.bind_all('<Control-z>', lambda event: self._undo())
        root.bind_all('<Control-y>', lambda event: self._redo())
        root.bind_all('<Control-o>', lambda event: self._openImage())
        root.bind_all('<Control-s>', lambda event: self._saveImage())
        root.bind_all('<Control-k>', lambda event: self._openCamera())
        root.bind_all('<Control-q>', lambda event: root.quit())
        root.bind_all('<Control-Shift-S>', lambda event: self._toggleSelectionMode())
        root.bind_all('<Control-Shift-C>', lambda event: self._toggleSelectionShape())
        root.bind_all('<Control-b>', lambda event: self._toggleBrushMode() if self._brushHandler else None)

        root.mainloop()

    def _openImage(self) -> None:
        file_path = filedialog.askopenfilename(
            filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif *.bmp")]
        )
        if not file_path:
            return

        self._currentImage = ImageContainer()
        self._currentImage.loadImage(file_path)
        self._renderCurrentImage()

    def _renderCurrentImage(self) -> None:
        if self._currentImage is None or self._currentImage.getImage() is None:
            return
        
        # Clear canvas
        self._imageCanvas.delete("all")
        
        pil_img = self._currentImage.getImage()
        width, height = pil_img.size
        tk_img = ImageTk.PhotoImage(pil_img)
        
        # Keep reference to avoid garbage collection
        self._imageLabel.image = tk_img
        
        # Store canvas image dimensions for coordinate conversion
        self._canvasImageWidth = width
        self._canvasImageHeight = height
        
        # Place image on canvas
        self._imageCanvas.create_image(0, 0, anchor="nw", image=tk_img)
        
        # Update canvas size
        self._imageCanvas.config(width=width, height=height)
        
        # If there's an active selection, draw it
        if self._areaSelectionHandler and self._areaSelectionHandler.selection_area.is_active and self._areaSelectionHandler.selection_area.is_valid():
            self._areaSelectionHandler.draw_selection_highlight()

    def _applyManipulation(self, manipulation, params: dict | None = None) -> None:
        if self._currentImage is None:
            return
        
        # Special handling for AddText - show dialog to get text input
        if manipulation.getManipulationName() == "Add Text" and params is None:
            if self._textEntryHandler:
                params = self._textEntryHandler.show_text_input_dialog()
                if params is None:  # User cancelled
                    return
        
        # Take undo snapshot
        self._currentImage.snapshot()
        if params is None:
            # default demo params for crop
            params = {"width": 200, "height": 200}
        manipulation.manipulateImage(self._currentImage, params)
        self._renderCurrentImage()

    def _saveImage(self) -> None:
        if self._currentImage is None or self._currentImage.getImage() is None:
            return
        path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[
            ("PNG", "*.png"), ("JPEG", "*.jpg;*.jpeg"), ("Bitmap", "*.bmp"), ("All Files", "*.*")
        ])
        if not path:
            return
        self._currentImage.saveImage(path)

    def _undo(self) -> None:
        if self._currentImage is None:
            return
        self._currentImage.undo()
        self._renderCurrentImage()

    def _redo(self) -> None:
        if self._currentImage is None:
            return
        self._currentImage.redo()
        self._renderCurrentImage()

    def _toggleSelectionMode(self) -> None:
        """Toggle interactive selection mode on/off."""
        if self._areaSelectionHandler:
            self._areaSelectionHandler.toggle_selection_mode()

    def _toggleSelectionShape(self) -> None:
        """Toggle selection shape between rectangle, circle, and lasso."""
        if self._areaSelectionHandler:
            self._areaSelectionHandler.toggle_selection_shape()

    def _onMouseClick(self, event) -> None:
        """Handle mouse click for selection or brush."""
        # Handle brush mode - delegate to handler
        if self._brushHandler and self._brushHandler.on_mouse_click(event):
            return
        
        # Handle selection mode - delegate to handler
        if self._areaSelectionHandler:
            self._areaSelectionHandler.on_mouse_click(event)

    def _onMouseDrag(self, event) -> None:
        """Handle mouse drag for selection or brush."""
        # Handle brush mode - delegate to handler
        if self._brushHandler and self._brushHandler.on_mouse_drag(event):
            return
        
        # Handle selection mode - delegate to handler
        if self._areaSelectionHandler:
            self._areaSelectionHandler.on_mouse_drag(event)

    def _onMouseRelease(self, event) -> None:
        """Handle mouse release to finalize selection or stop brush."""
        # Handle brush mode - delegate to handler
        if self._brushHandler and self._brushHandler.on_mouse_release(event):
            return
        
        # Handle selection mode - delegate to handler
        if self._areaSelectionHandler:
            self._areaSelectionHandler.on_mouse_release(event)

    def _onRightClick(self, event) -> None:
        """Handle right-click to show context menu for manipulation options."""
        if self._areaSelectionHandler:
            self._areaSelectionHandler.on_right_click(event, self._getDefaultParams)


    def _getDefaultParams(self, manipulation) -> dict:
        """Get default parameters for a manipulation."""
        manipulation_name = manipulation.getManipulationName().lower()
        
        if "crop" in manipulation_name:
            return {"width": 200, "height": 200}
        elif "resize" in manipulation_name:
            return {"width": 200, "height": 200}
        elif "rotate" in manipulation_name:
            return {"angle": 90}
        elif "flip" in manipulation_name:
            return {"mode": "horizontal"}
        elif "color" in manipulation_name or "adjust" in manipulation_name:
            return {"brightness": 10, "contrast": 1.2, "saturation": 1.1}
        elif "blur" in manipulation_name:
            return {"ksize": 5}
        elif "hue" in manipulation_name:
            return {"hue": 50}
        elif "padding" in manipulation_name:
            return {"border_width": 50}
        elif "sobel" in manipulation_name:
            return {"dx": 1, "dy": 0, "ksize": 3}
        elif "threshold" in manipulation_name:
            return {"thresh": 127}
        elif "text" in manipulation_name or "add text" in manipulation_name:
            return {"text": "Hello World", "x": 10, "y": 10, "font_size": 40, "color": (0, 0, 0)}
        else:
            return {}



    def _toggleBrushMode(self) -> None:
        """Toggle brush mode on/off."""
        if self._brushHandler:
            self._brushHandler.toggle_brush_mode()


    def _openCamera(self):
        camera = CameraWindow()
        camera.run(self)