# FlightMonitor/map/map_render_manager.py
"""
Manages the background worker thread responsible for rendering map images.
Handles communication (requests, results) with the worker thread.
"""
import threading
import queue
import time
from typing import Optional, Dict, Any, List, Tuple, Callable, Union, TYPE_CHECKING 

# Import PIL/ImageTk types for type hinting without direct import of modules that might be missing
try:
    from PIL import Image, ImageTk
    PIL_IMAGE_TYPES_AVAILABLE = True
    ImageType = Image.Image # Define ImageType alias
    PhotoImageType = ImageTk.PhotoImage # Define PhotoImageType alias
except ImportError:
    PIL_IMAGE_TYPES_AVAILABLE = False
    ImageType = Any # Fallback to Any if PIL is not installed
    PhotoImageType = Any # Fallback to Any if PIL is not installed

from flightmonitor.utils.logger import get_logger

# Type checking imports for circular dependencies (if needed, e.g., MapCanvasManager)
if TYPE_CHECKING:
    from flightmonitor.map.map_canvas_manager import MapCanvasManager # For type hinting

module_logger = get_logger(__name__)

# Constants for map worker
MAP_WORKER_QUEUE_TIMEOUT_S = 0.1 # Timeout for getting items from worker queue
RENDER_REQUEST_TYPE_MAP = "render_map" # Type of request for map rendering
RENDER_REQUEST_TYPE_SHUTDOWN = "shutdown_worker" # Type of request to shut down worker


class MapRenderManager:
    """
    Manages a dedicated worker thread for processing map rendering requests.
    Handles queuing of requests, retrieving rendered results, and worker shutdown.
    """

    def __init__(self):
        """
        Initializes the MapRenderManager.
        Sets up the queues and the worker thread.
        """
        self._map_render_request_queue: queue.Queue[Dict[str, Any]] = queue.Queue(maxsize=5) # Requests from GUI thread to worker
        self._map_render_result_queue: queue.Queue[Dict[str, Any]] = queue.Queue(maxsize=5) # Results from worker to GUI thread
        self._map_worker_stop_event: threading.Event = threading.Event() # Event to signal worker to stop
        self._map_worker_thread: Optional[threading.Thread] = None # The worker thread instance

        # Used for tracking requests/results to avoid processing stale data
        self._last_render_request_id: int = 0
        self._expected_render_id_gui: int = 0 # Managed by GUI part of MapCanvasManager, but stored here for context

        # The actual rendering pipeline function will be passed from MapCanvasManager
        # to avoid circular imports and keep this manager focused on thread/queue logic.
        self._render_pipeline_callable: Optional[Callable[..., Tuple[Optional[PhotoImageType], Optional[Tuple[float, ...]], Optional[str]]]] = None

        module_logger.debug("MapRenderManager initialized.")

    def set_render_pipeline_callable(self, render_pipeline_callable: Callable[..., Tuple[Optional[PhotoImageType], Optional[Tuple[float, ...]], Optional[str]]]):
        """
        Sets the callable function that the worker thread will execute for rendering.
        This function should typically be MapCanvasManager._execute_render_pipeline.

        Args:
            render_pipeline_callable: The function responsible for executing the map rendering pipeline.
                                      It should return (PhotoImage, map_geo_bounds, error_message).
        """
        self._render_pipeline_callable = render_pipeline_callable
        module_logger.debug("MapRenderManager: Render pipeline callable set.")

    def start_worker(self):
        """
        Starts the background map rendering worker thread.
        If a worker is already running, it logs a warning.
        """
        if self._map_worker_thread is not None and self._map_worker_thread.is_alive():
            module_logger.warning("MapRenderManager: Map worker thread already running.")
            return

        self._map_worker_stop_event.clear() # Clear stop signal for new start
        self._map_worker_thread = threading.Thread(
            target=self._map_render_worker_target,
            name="MapRenderWorker",
            daemon=True # Daemon thread will exit when main program exits
        )
        self._map_worker_thread.start()
        module_logger.info("MapRenderManager: Map rendering worker thread started successfully.")

    def shutdown_worker(self):
        """
        Signals the worker thread to stop and waits for it to finish.
        Also clears any pending requests/results in the queues.
        """
        module_logger.info("MapRenderManager: Shutdown worker requested.")
        self._map_worker_stop_event.set() # Signal the worker thread to stop

        # Attempt to put a shutdown request in the queue to unblock the worker if it's waiting
        try:
            self._map_render_request_queue.put_nowait({"type": RENDER_REQUEST_TYPE_SHUTDOWN, "request_id": -999})
        except queue.Full:
            module_logger.warning("MapRenderManager: Worker request queue full, cannot send shutdown signal via queue.")
        except Exception as e:
            module_logger.error(f"MapRenderManager: Error sending shutdown signal to worker queue: {e}")

        # Wait for the worker thread to finish
        if self._map_worker_thread and self._map_worker_thread.is_alive():
            module_logger.info("MapRenderManager: Waiting for map worker thread to join...")
            self._map_worker_thread.join(timeout=2.0) # Wait for max 2 seconds
            if self._map_worker_thread.is_alive():
                module_logger.warning("MapRenderManager: Map worker thread did not join in time.")
            else:
                module_logger.info("MapRenderManager: Map worker thread joined successfully.")
        self._map_worker_thread = None # Clear thread reference

        # Clear any remaining items in queues
        while not self._map_render_request_queue.empty():
            try: self._map_render_request_queue.get_nowait(); self._map_render_request_queue.task_done()
            except Exception: break
        while not self._map_render_result_queue.empty():
            try: self._map_render_result_queue.get_nowait(); self._map_render_result_queue.task_done()
            except Exception: break
        module_logger.info("MapRenderManager: Worker shutdown sequence complete.")

    def _map_render_worker_target(self):
        """
        The main loop for the background map rendering worker thread.
        Pulls rendering requests from the queue and executes the rendering pipeline.
        """
        thread_name = threading.current_thread().name
        module_logger.debug(f"{thread_name}: Worker thread target loop started.")

        if not self._render_pipeline_callable:
            module_logger.critical(f"{thread_name}: Render pipeline callable not set. Worker cannot function. Exiting.")
            return

        while not self._map_worker_stop_event.is_set():
            request_data = None
            request_id = -1
            try:
                # Wait for a request or until stop event is set (with timeout for responsiveness)
                request_data = self._map_render_request_queue.get(timeout=MAP_WORKER_QUEUE_TIMEOUT_S)
                request_type = request_data.get("type")
                request_id = request_data.get("request_id", -1)
                # module_logger.debug(f"{thread_name}: Dequeued request. Type: '{request_type}', ID: {request_id}")

                if request_type == RENDER_REQUEST_TYPE_SHUTDOWN:
                    module_logger.info(f"{thread_name}: Received shutdown request (ID: {request_id}). Terminating.")
                    self._map_render_request_queue.task_done()
                    break # Exit the worker loop

                if request_type == RENDER_REQUEST_TYPE_MAP:
                    # module_logger.debug(f"{thread_name}: Processing RENDER_REQUEST_TYPE_MAP for ID: {request_id}")
                    
                    # Extract all arguments needed for the rendering pipeline
                    center_lat = request_data.get("center_lat")
                    center_lon = request_data.get("center_lon")
                    zoom = request_data.get("zoom")
                    canvas_w = request_data.get("canvas_width")
                    canvas_h = request_data.get("canvas_height")
                    target_bbox_to_center_and_draw = request_data.get("target_bbox")
                    draw_target_bbox_overlay_flag = request_data.get("draw_target_bbox_overlay", False)
                    flights_to_draw = request_data.get("flights", [])
                    tracks_to_draw = request_data.get("tracks", {})
                    max_track_pts_from_req = request_data.get("max_track_points", 20) # Default to 20 if not in request

                    if None in [center_lat, center_lon, zoom, canvas_w, canvas_h]:
                        error_message = f"Missing critical rendering parameters for ID {request_id}."
                        module_logger.error(f"{thread_name}: {error_message}")
                        result_payload_err = {"request_id": request_id, "photo_image": None, "map_geo_bounds": None, "error": error_message}
                        try: self._map_render_result_queue.put_nowait(result_payload_err)
                        except queue.Full: module_logger.error(f"{thread_name}: Result queue full, dropped error for ID {request_id}.")
                        self._map_render_request_queue.task_done()
                        continue # Skip to next request

                    # Execute the actual rendering pipeline (provided by MapCanvasManager)
                    photo_image_result, actual_map_bounds, error_message = self._render_pipeline_callable(
                        center_lat, center_lon, zoom, canvas_w, canvas_h,
                        target_bbox_to_center_and_draw, 
                        draw_target_bbox_overlay_flag,
                        flights_to_draw, tracks_to_draw, max_track_pts_from_req
                    )

                    # Check for stop signal again after a potentially long rendering operation
                    if self._map_worker_stop_event.is_set():
                        module_logger.info(f"{thread_name}: Stop event set after render pipeline for ID {request_id}. Discarding result.")
                        break # Exit the worker loop

                    # Put the rendering result into the result queue for the GUI thread
                    result_payload = {
                        "request_id": request_id, "photo_image": photo_image_result,
                        "map_geo_bounds": actual_map_bounds, "error": error_message,
                    }
                    try:
                        self._map_render_result_queue.put_nowait(result_payload)
                        # module_logger.debug(f"{thread_name}: Successfully put result for ID {request_id} into queue.")
                    except queue.Full:
                        module_logger.error(f"{thread_name}: Result queue full for ID {request_id}. Result discarded.")
                    except Exception as e_put:
                        module_logger.error(f"{thread_name}: Error putting result for ID {request_id} into queue: {e_put}")

                else:
                    module_logger.warning(f"{thread_name}: Received unknown request type '{request_type}' for ID {request_id}.")
                
                self._map_render_request_queue.task_done() # Mark task as done for this request

            except queue.Empty:
                # No requests in queue, just continue to check stop event
                continue
            except Exception as e:
                # Catch any unhandled exceptions during request processing
                module_logger.exception(f"{thread_name}: Unhandled exception in worker loop for request ID {request_id if request_data else 'N/A'}: {e}")
                # Try to report the error back to the GUI
                if request_data and request_id != -1:
                    error_payload_exc = {"request_id": request_id, "photo_image": None, "map_geo_bounds": None, "error": f"Worker loop unhandled exception: {type(e).__name__}"}
                    try:
                        self._map_render_result_queue.put_nowait(error_payload_exc)
                    except queue.Full:
                        module_logger.error(f"{thread_name}: Result queue full while trying to report worker unhandled exception.")
                    except Exception as e_put_err:
                         module_logger.error(f"{thread_name}: Error putting unhandled exception report to result queue: {e_put_err}")
                # Ensure the task is marked done if an exception occurred after getting it
                if request_data:
                    try: self._map_render_request_queue.task_done()
                    except ValueError: pass # task_done() might raise if called too many times
                time.sleep(0.5) # Brief pause after an error to prevent tight looping on persistent errors

        module_logger.info(f"{thread_name}: Worker thread target loop finished.")

    def put_render_request(self, request_payload: Dict[str, Any]) -> Optional[int]:
        """
        Puts a map rendering request into the worker's request queue.
        Assigns a unique request ID.

        Args:
            request_payload: A dictionary containing all parameters for rendering.

        Returns:
            Optional[int]: The ID of the request if successfully queued, None otherwise.
        """
        self._last_render_request_id += 1 # Increment request ID
        current_request_id = self._last_render_request_id
        request_payload["request_id"] = current_request_id # Add ID to payload
        
        try:
            self._map_render_request_queue.put_nowait(request_payload)
            module_logger.debug(f"MapRenderManager: Queued render request ID {current_request_id}.")
            return current_request_id
        except queue.Full:
            module_logger.warning(f"MapRenderManager: Render request queue FULL. Request ID {current_request_id} was DROPPED.")
            return None
        except Exception as e:
            module_logger.error(f"MapRenderManager: Error putting render request ID {current_request_id} into queue: {e}")
            return None

    def get_render_result(self) -> Optional[Dict[str, Any]]:
        """
        Attempts to retrieve a map rendering result from the worker's result queue.
        This method should be called periodically by the GUI thread.

        Returns:
            Optional[Dict[str, Any]]: A dictionary containing the rendering result, or None if no result is available.
        """
        try:
            result = self._map_render_result_queue.get_nowait()
            self._map_render_result_queue.task_done()
            return result
        except queue.Empty:
            return None
        except Exception as e:
            module_logger.error(f"MapRenderManager: Error getting render result from queue: {e}")
            return None

    def get_expected_gui_render_id(self) -> int:
        """
        Returns the ID of the last request that the GUI expects to process.
        This is set by MapCanvasManager before queuing a request.
        """
        return self._expected_render_id_gui

    def set_expected_gui_render_id(self, request_id: int):
        """
        Sets the ID of the last request that the GUI expects to process.
        Called by MapCanvasManager when it queues a new request.
        """
        self._expected_render_id_gui = request_id
        module_logger.debug(f"MapRenderManager: Expected GUI render ID set to {request_id}.")

    def is_worker_alive(self) -> bool:
        """Checks if the worker thread is currently alive."""
        return self._map_worker_thread is not None and self._map_worker_thread.is_alive()