# --- START OF FILE map_display.py ---

# map_display.py
"""
Manages the dedicated OpenCV window for displaying the map overlay.
Handles window creation, positioning, image updates, and destruction.
"""

# Standard library imports
import logging
from typing import Optional  # <<< AGGIUNTO QUESTO IMPORT

# Third-party imports
import cv2
import numpy as np

try:
    from PIL import Image  # For converting Pillow image to NumPy
except ImportError:
    Image = None

# Local application imports
import config  # For placeholder color


class MapDisplayWindow:
    """Manages the OpenCV window used to display the map."""

    MAX_DISPLAY_WIDTH = config.MAX_MAP_DISPLAY_WIDTH
    MAX_DISPLAY_HEIGHT = config.MAX_MAP_DISPLAY_HEIGHT

    def __init__(
        self, window_name: str = "Map Overlay", x_pos: int = 100, y_pos: int = 100
    ):
        """
        Initializes the MapDisplayWindow manager.

        Args:
            window_name (str): The name for the OpenCV window.
            x_pos (int): Initial X position for the window.
            y_pos (int): Initial Y position for the window.
        """
        self._log_prefix = "[MapDisplay]"
        if Image is None:
            logging.critical(
                f"{self._log_prefix} 'Pillow' library is required but not found. Map display might fail."
            )
            # Allow init but warn heavily, functionality will be broken
            # raise ImportError("'Pillow' library not found.")

        self.window_name = window_name
        self.x_pos = x_pos
        self.y_pos = y_pos
        self.window_initialized = False
        self._current_shape = (0, 0)  # Store shape to avoid unnecessary moves
        logging.debug(
            f"{self._log_prefix} Initializing window manager for '{self.window_name}'."
        )

    def show_map(self, map_image_pil: Optional[Image.Image]):
        """
        Displays the provided map image (Pillow format) in the OpenCV window.
        Handles window creation, positioning, resizing, and placeholders.

        Args:
            map_image_pil (Optional[Image.Image]): The map image (Pillow format) to display.
                                                   If None, shows a placeholder/error image.
        """
        log_prefix = f"{self._log_prefix} ShowMap"  # Specific prefix for this method

        # --- Logging Input ---
        if map_image_pil is None:
            logging.warning(f"{log_prefix} Received None image payload.")
        elif Image is not None and isinstance(map_image_pil, Image.Image):
            logging.debug(
                f"{log_prefix} Received PIL Image payload (Size: {map_image_pil.size}, Mode: {map_image_pil.mode})."
            )
        else:
            # Log unexpected payload types
            logging.error(
                f"{log_prefix} Received unexpected payload type: {type(map_image_pil)}. Cannot display."
            )
            # Attempt to show placeholder instead of crashing
            map_image_pil = None  # Force placeholder generation

        # Check for Pillow dependency again, crucial for processing
        if Image is None:
            logging.error(
                f"{log_prefix} Cannot process map: Pillow library not loaded."
            )
            # Maybe display a permanent error in the window if possible? Difficult without cv2.
            return

        map_to_display_bgr = None

        # --- Handle None Payload: Create Placeholder ---
        if map_image_pil is None:
            logging.warning(f"{log_prefix} Generating placeholder image.")
            try:
                # Use fixed size for placeholder for simplicity, or config value?
                placeholder_size = (512, 512)
                placeholder_color_rgb = getattr(
                    config, "OFFLINE_MAP_PLACEHOLDER_COLOR", (200, 200, 200)
                )
                # Ensure color is valid tuple
                if not (
                    isinstance(placeholder_color_rgb, tuple)
                    and len(placeholder_color_rgb) == 3
                ):
                    placeholder_color_rgb = (200, 200, 200)  # Fallback grey

                placeholder_pil = Image.new(
                    "RGB", placeholder_size, color=placeholder_color_rgb
                )
                # Add text indication? Requires Pillow draw, adds complexity. Keep simple for now.
                # from PIL import ImageDraw
                # draw = ImageDraw.Draw(placeholder_pil)
                # draw.text((10, 10), "Map Load Failed", fill=(0,0,0))

                # Convert placeholder PIL to NumPy BGR immediately
                placeholder_np = np.array(placeholder_pil)
                map_to_display_bgr = cv2.cvtColor(placeholder_np, cv2.COLOR_RGB2BGR)
                logging.debug(
                    f"{log_prefix} Placeholder generated (BGR Shape: {map_to_display_bgr.shape})."
                )
            except Exception as ph_err:
                logging.exception(
                    f"{log_prefix} Failed to create or convert placeholder image:"
                )
                # If even placeholder fails, create a very basic numpy array as last resort
                map_to_display_bgr = np.full(
                    (256, 256, 3), 60, dtype=np.uint8
                )  # Dark grey small square
                logging.error(
                    f"{log_prefix} Using minimal NumPy array as placeholder fallback."
                )

        # --- Convert Valid PIL Image Payload to NumPy BGR ---
        if map_to_display_bgr is None:  # Only if placeholder wasn't created above
            try:
                # Convert PIL Image (expected RGB) to NumPy array
                map_image_np = np.array(map_image_pil)
                # Ensure it's 3-channel (handle grayscale PIL potentially)
                if map_image_np.ndim == 2:
                    # Convert grayscale numpy to BGR
                    map_to_display_bgr = cv2.cvtColor(map_image_np, cv2.COLOR_GRAY2BGR)
                    logging.debug(
                        f"{log_prefix} Converted Grayscale PIL to NumPy BGR (Shape: {map_to_display_bgr.shape})."
                    )
                elif map_image_np.ndim == 3 and map_image_np.shape[2] == 3:
                    # Convert RGB (from Pillow) to BGR (for OpenCV)
                    map_to_display_bgr = cv2.cvtColor(map_image_np, cv2.COLOR_RGB2BGR)
                    logging.debug(
                        f"{log_prefix} Converted RGB PIL to NumPy BGR (Shape: {map_to_display_bgr.shape})."
                    )
                elif map_image_np.ndim == 3 and map_image_np.shape[2] == 4:
                    # Convert RGBA (from Pillow) to BGR (for OpenCV), discarding alpha
                    map_to_display_bgr = cv2.cvtColor(map_image_np, cv2.COLOR_RGBA2BGR)
                    logging.debug(
                        f"{log_prefix} Converted RGBA PIL to NumPy BGR (Shape: {map_to_display_bgr.shape})."
                    )
                else:
                    raise ValueError(
                        f"Unsupported NumPy array shape after PIL conversion: {map_image_np.shape}"
                    )

            except Exception as e:
                logging.exception(
                    f"{log_prefix} Error converting received PIL image to OpenCV BGR format:"
                )
                # Fallback to basic numpy array on conversion error
                map_to_display_bgr = np.full((256, 256, 3), 60, dtype=np.uint8)
                logging.error(
                    f"{log_prefix} Using minimal NumPy array as fallback due to conversion error."
                )

        # --- Resize Image if Exceeds Max Dimensions ---
        try:
            img_h, img_w = map_to_display_bgr.shape[:2]
            if img_h > self.MAX_DISPLAY_HEIGHT or img_w > self.MAX_DISPLAY_WIDTH:
                logging.debug(
                    f"{log_prefix} Image ({img_w}x{img_h}) exceeds max size ({self.MAX_DISPLAY_WIDTH}x{self.MAX_DISPLAY_HEIGHT}). Resizing..."
                )
                # Calculate aspect ratio
                ratio = min(
                    self.MAX_DISPLAY_WIDTH / img_w, self.MAX_DISPLAY_HEIGHT / img_h
                )
                new_w = int(img_w * ratio)
                new_h = int(img_h * ratio)
                # Resize using OpenCV - INTER_AREA is generally good for downscaling
                map_to_display_bgr = cv2.resize(
                    map_to_display_bgr, (new_w, new_h), interpolation=cv2.INTER_AREA
                )
                logging.debug(f"{log_prefix} Resized map image to {new_w}x{new_h}.")
            else:
                logging.debug(
                    f"{log_prefix} Image size ({img_w}x{img_h}) is within limits. No resize needed."
                )
        except Exception as resize_err:
            logging.exception(f"{log_prefix} Error during map image resizing:")
            # Continue with the unresized image if resize fails

        # --- Display using OpenCV ---
        try:
            # Log the final shape before showing
            final_shape = map_to_display_bgr.shape
            logging.debug(
                f"{log_prefix} Attempting cv2.imshow with final image shape: {final_shape}"
            )

            new_shape = final_shape[:2]  # (height, width)

            # Create and move window only once or if shape changes drastically
            if not self.window_initialized or new_shape != self._current_shape:
                logging.debug(
                    f"{log_prefix} First show or shape change for '{self.window_name}'. Creating/moving window."
                )
                cv2.imshow(self.window_name, map_to_display_bgr)
                try:
                    cv2.moveWindow(self.window_name, self.x_pos, self.y_pos)
                    self.window_initialized = True
                    self._current_shape = new_shape
                    logging.info(
                        f"{log_prefix} Window '{self.window_name}' shown/moved to ({self.x_pos}, {self.y_pos})."
                    )
                    # Allow window to draw/position - waitKey might be handled by caller loop
                    # cv2.waitKey(1)
                except cv2.error as move_e:
                    logging.warning(
                        f"{log_prefix} Could not move '{self.window_name}' window: {move_e}."
                    )
                    self.window_initialized = True  # Assume imshow worked
                    self._current_shape = new_shape  # Update shape even if move failed

            else:
                # Just update the image content if window exists and shape is same
                logging.debug(
                    f"{log_prefix} Updating existing window '{self.window_name}' content."
                )
                cv2.imshow(self.window_name, map_to_display_bgr)

            # Essential waitKey to process OpenCV events if called outside main loop,
            # but likely handled by the calling queue processor loop (process_tkinter_queue).
            # cv2.waitKey(1)

        except cv2.error as e:
            # Handle OpenCV errors (e.g., window closed manually)
            if "NULL window" in str(e) or "invalid window" in str(e):
                logging.warning(
                    f"{log_prefix} OpenCV window '{self.window_name}' seems closed. Will re-initialize on next valid image."
                )
                self.window_initialized = False  # Reset flag
            else:
                # Log other OpenCV errors during display
                logging.exception(
                    f"{log_prefix} OpenCV error during final map display (imshow): {e}"
                )
        except Exception as e:
            # Log other unexpected errors during display
            logging.exception(
                f"{log_prefix} Unexpected error displaying final map image: {e}"
            )

    # --- destroy_window method remains the same ---
    def destroy_window(self):
        """Explicitly destroys the managed OpenCV window."""
        logging.info(
            f"{self._log_prefix} Attempting to destroy window: '{self.window_name}'"
        )
        if self.window_initialized:
            try:
                cv2.destroyWindow(self.window_name)
                self.window_initialized = False
                logging.info(
                    f"{self._log_prefix} Window '{self.window_name}' destroyed successfully."
                )
            except cv2.error as e:
                logging.warning(
                    f"{self._log_prefix} Ignoring error destroying window '{self.window_name}' (may already be closed): {e}"
                )
            except Exception as e:
                logging.exception(
                    f"{self._log_prefix} Unexpected error destroying window '{self.window_name}': {e}"
                )
        else:
            logging.debug(
                f"{self._log_prefix} Window '{self.window_name}' was not initialized or already destroyed."
            )


# --- END OF FILE map_display.py ---
