"""
ElevatorSystem.py - Main elevator system controller with Singleton pattern
Implements SOLID: Single Responsibility (orchestration only)
                  Open/Closed (dispatcher strategy is pluggable)
                  Dependency Inversion (depends on DispatcherStrategy)
"""

from typing import List, Optional, Dict
from Direction import Direction, ElevatorState
from ElevatorCar import ElevatorCar
from ElevatorPanel import Floor
from Display import Display
from Dispatcher import DispatcherStrategy, NearestIdleDispatcher


class Building:
    """
    Represents a building with multiple floors and elevator cars.
    
    SOLID: Single Responsibility - manages building structure only
    """

    def __init__(self, num_floors: int, num_cars: int):
        """
        Initialize building.
        
        Args:
            num_floors: Number of floors (0 to num_floors-1)
            num_cars: Number of elevator cars
        """
        self.num_floors = num_floors
        self.num_cars = num_cars

        # Create elevator cars
        self.cars: List[ElevatorCar] = []
        for car_id in range(num_cars):
            car = ElevatorCar(car_id=car_id, num_floors=num_floors)
            self.cars.append(car)

        # Create floors with hall panels
        self.floors: List[Floor] = []
        for floor_num in range(num_floors):
            floor = Floor(floor_number=floor_num, num_floors=num_floors)
            self.floors.append(floor)

    def get_floor(self, floor_num: int) -> Optional[Floor]:
        """Get floor by number."""
        if 0 <= floor_num < self.num_floors:
            return self.floors[floor_num]
        return None

    def get_floors(self) -> List[Floor]:
        """Get all floors."""
        return self.floors.copy()

    def get_cars(self) -> List[ElevatorCar]:
        """Get all elevator cars."""
        return self.cars.copy()

    def get_car(self, car_id: int) -> Optional[ElevatorCar]:
        """Get specific car by ID."""
        if 0 <= car_id < len(self.cars):
            return self.cars[car_id]
        return None

    def __str__(self) -> str:
        return f"Building(floors={self.num_floors}, cars={self.num_cars})"


class ElevatorSystem:
    """
    Main elevator system controller.
    
    Design Pattern: Singleton
    - Only one instance of system exists
    - Centralized control and monitoring
    - Single point of access
    
    Design Pattern: Strategy (for dispatcher)
    - Pluggable dispatch algorithms
    - Easy to switch strategies at runtime
    
    SOLID Principles:
    - Single Responsibility: Orchestrates system only
    - Open/Closed: Dispatcher strategy is extensible
    - Dependency Inversion: Depends on DispatcherStrategy interface
    - Liskov Substitution: Any DispatcherStrategy works
    """

    _instance: Optional['ElevatorSystem'] = None

    def __init__(self, num_floors: int, num_cars: int):
        """
        Initialize elevator system (private - use get_instance).
        
        Args:
            num_floors: Number of floors in building
            num_cars: Number of elevator cars
        """
        self.building = Building(num_floors=num_floors, num_cars=num_cars)
        self._dispatcher: DispatcherStrategy = NearestIdleDispatcher()

    @staticmethod
    def get_instance(num_floors: int = 10, num_cars: int = 3) -> 'ElevatorSystem':
        """
        Get singleton instance of elevator system.
        
        SOLID: Factory method - centralized object creation
        
        Args:
            num_floors: Number of floors (used only on first call)
            num_cars: Number of cars (used only on first call)
            
        Returns:
            Singleton ElevatorSystem instance
        """
        if ElevatorSystem._instance is None:
            ElevatorSystem._instance = ElevatorSystem(num_floors, num_cars)
        return ElevatorSystem._instance

    @staticmethod
    def reset_instance() -> None:
        """Reset singleton (useful for testing)."""
        ElevatorSystem._instance = None

    # ==================== Dispatcher Management ====================

    def set_dispatcher(self, dispatcher: DispatcherStrategy) -> None:
        """
        Set the dispatch strategy.
        
        SOLID: Open/Closed - plug in different algorithms
        
        Args:
            dispatcher: New dispatcher strategy to use
        """
        self._dispatcher = dispatcher

    def get_dispatcher(self) -> DispatcherStrategy:
        """Get current dispatcher strategy."""
        return self._dispatcher

    # ==================== Call Handling ====================

    def call_elevator(self, floor: int, direction: Direction) -> Optional[ElevatorCar]:
        """
        Process a hall call from a specific floor.
        
        Args:
            floor: Floor requesting elevator
            direction: Direction requested (UP/DOWN)
            
        Returns:
            Assigned ElevatorCar, or None if no suitable car
        """
        # Validate floor
        if not (0 <= floor < self.building.num_floors):
            return None

        # Dispatch suitable car
        car_index = self._dispatcher.dispatch(floor, direction, self.building.cars)

        if car_index is None:
            return None

        assigned_car = self.building.cars[car_index]

        # Register request with car
        assigned_car.register_request(floor=floor, direction=direction)

        return assigned_car

    def register_floor_request(self, car_id: int, floor: int) -> bool:
        """
        Register a floor selection request inside an elevator.
        
        Args:
            car_id: ID of elevator car
            floor: Destination floor
            
        Returns:
            True if request was accepted
        """
        car = self.building.get_car(car_id)
        if car is None:
            return False

        return car.register_request(floor=floor, direction=Direction.IDLE)

    # ==================== Elevator Control ====================

    def put_elevator_in_maintenance(self, car_id: int) -> bool:
        """
        Put an elevator into maintenance mode.
        
        Args:
            car_id: ID of elevator to maintain
            
        Returns:
            True if successful
        """
        car = self.building.get_car(car_id)
        if car is None:
            return False

        car.enter_maintenance()
        return True

    def release_elevator_from_maintenance(self, car_id: int) -> bool:
        """
        Release an elevator from maintenance mode.
        
        Args:
            car_id: ID of elevator
            
        Returns:
            True if successful
        """
        car = self.building.get_car(car_id)
        if car is None:
            return False

        car.exit_maintenance()
        return True

    def emergency_stop(self, car_id: int) -> bool:
        """
        Trigger emergency stop on specific car.
        
        Args:
            car_id: ID of elevator
            
        Returns:
            True if successful
        """
        car = self.building.get_car(car_id)
        if car is None:
            return False

        car.emergency_stop()
        return True

    def reset_emergency(self, car_id: int) -> bool:
        """
        Reset emergency stop on specific car.
        
        Args:
            car_id: ID of elevator
            
        Returns:
            True if successful
        """
        car = self.building.get_car(car_id)
        if car is None:
            return False

        car.reset_emergency()
        return True

    # ==================== Movement Simulation ====================

    def move_car_one_floor(self, car_id: int) -> bool:
        """
        Simulate elevator moving one floor.
        
        Args:
            car_id: ID of car to move
            
        Returns:
            True if movement was simulated
        """
        car = self.building.get_car(car_id)
        if car is None:
            return False

        car.move_one_floor()
        return True

    def move_all_cars(self) -> int:
        """
        Move all active cars one floor each.
        
        Returns:
            Number of cars moved
        """
        count = 0
        for car in self.building.cars:
            if car.get_state() in (ElevatorState.MOVING_UP, ElevatorState.MOVING_DOWN):
                car.move_one_floor()
                count += 1
        return count

    def depart_floor(self, car_id: int) -> bool:
        """
        Depart from current floor and move to next request.
        
        Args:
            car_id: ID of car
            
        Returns:
            True if successful
        """
        car = self.building.get_car(car_id)
        if car is None:
            return False

        car.depart_floor()
        return True

    # ==================== System Status ====================

    def get_system_status(self) -> Dict:
        """
        Get complete system status.
        
        Returns:
            Dictionary with building and all cars status
        """
        return {
            "building": str(self.building),
            "dispatcher": self._dispatcher.__class__.__name__,
            "cars": [car.get_status() for car in self.building.cars],
        }

    def get_car_status(self, car_id: int) -> Optional[Dict]:
        """Get status of specific car."""
        car = self.building.get_car(car_id)
        return car.get_status() if car else None

    def get_all_cars_status(self) -> List[Dict]:
        """Get status of all cars."""
        return [car.get_status() for car in self.building.cars]

    def print_system_status(self) -> None:
        """Print system status to console."""
        print("\n" + "=" * 60)
        print(f"  {self.building}")
        print(f"  Dispatcher: {self._dispatcher.__class__.__name__}")
        print("=" * 60)

        for car in self.building.cars:
            print(f"  {car}")

        print("=" * 60 + "\n")

    # ==================== Building Access ====================

    def get_building(self) -> Building:
        """Get building reference."""
        return self.building

    def get_elevators(self) -> List[ElevatorCar]:
        """Get list of all elevator cars."""
        return self.building.get_cars()

    def get_floors(self) -> List[Floor]:
        """Get list of all floors."""
        return self.building.get_floors()

    def get_floor(self, floor_num: int) -> Optional[Floor]:
        """Get specific floor."""
        return self.building.get_floor(floor_num)

    # ==================== Display Integration ====================

    def subscribe_display_to_car(self, car_id: int, display: Display) -> bool:
        """
        Subscribe a display to elevator car state changes.
        
        Args:
            car_id: ID of car
            display: Display object to subscribe
            
        Returns:
            True if successful
        """
        car = self.building.get_car(car_id)
        if car is None:
            return False

        car.subscribe(display)
        return True

    def get_display_for_car(self, car_id: int) -> Optional[Display]:
        """Get built-in display for a car."""
        car = self.building.get_car(car_id)
        return car.get_display() if car else None

    # ==================== Statistics ====================

    def get_total_pending_requests(self) -> int:
        """Get total requests across all cars."""
        return sum(car.get_request_queue_size() for car in self.building.cars)

    def get_idle_cars_count(self) -> int:
        """Count idle cars."""
        return sum(1 for car in self.building.cars if car.get_state() == ElevatorState.IDLE)

    def get_moving_cars_count(self) -> int:
        """Count cars currently moving."""
        return sum(1 for car in self.building.cars if car.get_state().is_moving())

    def get_maintenance_cars_count(self) -> int:
        """Count cars in maintenance."""
        return sum(1 for car in self.building.cars if car.is_in_maintenance())

    # ==================== String Representation ====================

    def __str__(self) -> str:
        return (
            f"ElevatorSystem(floors={self.building.num_floors}, "
            f"cars={self.building.num_cars}, "
            f"dispatcher={self._dispatcher.__class__.__name__})"
        )