# target_simulator/core/simulation_engine.py

"""
The core simulation engine that runs in a separate thread to update and
broadcast target states, supporting different operational modes.
"""
import threading
import time
from queue import Queue
from typing import Optional

# Use absolute imports for robustness
from target_simulator.core.communicator_interface import CommunicatorInterface
from target_simulator.core.models import Scenario
from target_simulator.core import command_builder
from target_simulator.utils.logger import get_logger

# Simulation frequency in Hertz
TICK_RATE_HZ = 20.0  # Increased for smoother seeking
TICK_INTERVAL_S = 1.0 / TICK_RATE_HZ


class SimulationEngine(threading.Thread):
    def __init__(
        self,
        communicator: Optional[CommunicatorInterface],
        update_queue: Optional[Queue],
    ):
        super().__init__(daemon=True, name="SimulationEngineThread")
        self.logger = get_logger(__name__)

        self.communicator = communicator
        self.update_queue = update_queue
        self.time_multiplier = 1.0
        self.update_interval_s = 1.0

        self.scenario: Optional[Scenario] = None
        self._stop_event = threading.Event()
        self._is_running_event = threading.Event()
        self._last_tick_time = 0.0
        self._last_update_time = 0.0
        self._is_paused = False

    def load_scenario(self, scenario: Scenario):
        """Loads a new scenario into the engine and resets its simulation state."""
        self.logger.info(f"Loading scenario '{scenario.name}' into simulation engine.")
        self.scenario = scenario
        self.scenario.reset_simulation()

    def set_time_multiplier(self, multiplier: float):
        """Sets the simulation time speed multiplier."""
        self.logger.info(f"Setting simulation time multiplier to {multiplier}x")
        self.time_multiplier = multiplier

    def set_update_interval(self, interval_s: float):
        """Sets the interval for sending target updates."""
        self.logger.info(f"Setting target update interval to {interval_s}s")
        self.update_interval_s = interval_s

    def set_simulation_time(self, new_time_s: float):
        """
        Jump the simulation to a specific point in time.
        This should be called while the simulation is paused for best results.
        """
        if self.scenario:
            self.logger.debug(f"Seeking simulation time to {new_time_s:.2f}s.")
            for target in self.scenario.get_all_targets():
                # Directly set the internal simulation time of the target
                setattr(target, "_sim_time_s", new_time_s)
                # Force an update to calculate the new position
                target.update_state(0)

            # Reset the tick time to avoid a large delta on the next frame
            self._last_tick_time = time.monotonic()

            # Push an immediate update to the GUI
            if self.update_queue:
                self.update_queue.put_nowait(self.scenario.get_all_targets())

    def run(self):
        """The main loop of the simulation thread."""
        self.logger.info("Simulation engine thread started.")
        self._is_running_event.set()
        self._last_tick_time = time.monotonic()
        self._last_update_time = self._last_tick_time

        while not self._stop_event.is_set():
            if not self.scenario:
                time.sleep(TICK_INTERVAL_S)
                continue

            if self._is_paused:
                # While paused, we still sleep to avoid a busy-wait loop
                time.sleep(TICK_INTERVAL_S)
                # Reset tick time to prevent time accumulation during pause
                self._last_tick_time = time.monotonic()
                continue

            # --- Time Management ---
            current_time = time.monotonic()
            delta_time = current_time - self._last_tick_time
            self._last_tick_time = current_time
            simulated_delta_time = delta_time * self.time_multiplier

            # --- Simulation Step ---
            self.scenario.update_state(simulated_delta_time)
            updated_targets = self.scenario.get_all_targets()

            # --- Check for simulation end ---
            if self.scenario.is_finished():
                self.logger.info("Scenario finished. Stopping engine.")
                # Send one final update to show the end state
                if self.update_queue:
                    self.update_queue.put_nowait(updated_targets)
                if self.update_queue:
                    self.update_queue.put_nowait("SIMULATION_FINISHED")
                break  # Exit the loop

            # --- Communication Step ---
            if self.communicator and self.communicator.is_open:
                if current_time - self._last_update_time >= self.update_interval_s:
                    self._last_update_time = current_time
                    # ... (communication logic is unchanged)

            # --- GUI Update Step ---
            if self.update_queue:
                try:
                    self.update_queue.put_nowait(updated_targets)
                except Queue.Full:
                    self.logger.warning(
                        "GUI update queue is full. A frame was skipped."
                    )

            time.sleep(TICK_INTERVAL_S)

        self._is_running_event.clear()
        self.logger.info("Simulation engine thread stopped.")

    def pause(self):
        """Pauses the simulation loop."""
        self.logger.info("Simulation paused.")
        self._is_paused = True

    def resume(self):
        """Resumes the simulation loop."""
        self.logger.info("Simulation resumed.")
        self._is_paused = False

    def stop(self):
        """Signals the simulation thread to stop."""
        self.logger.info("Stop signal received for simulation thread.")
        self._stop_event.set()
        # Ensure it unpauses to read the stop event
        self.resume()
        self.join(timeout=2.0)
        if self.is_alive():
            self.logger.warning("Simulation thread did not stop gracefully.")

    def is_running(self) -> bool:
        """Checks if the simulation loop is currently active."""
        return self._is_running_event.is_set()
