class SimulationEngine():
    """
    Class responsible for running the simulation by working with the Building,
    Passengers, and Elevator classes

    Attributes:
        num_floors: number of floors in the building indexed from 0 to num_floors-1
        passenger_list: list of passengers
        elevator: elevator class used in the building
        building: building class
    """

    END_MESSAGE = "All passengers have been served - End of simulation"
    TIME_MESSAGE = "Average journey time: %s"
    MOVE_MESSAGE = "Elevator moved from floor %s to floor %s"
    PICKUP_MESSAGE = "Elevator picks up passenger %s at floor %s"
    DROPOFF_MESSAGE = "Elevator drops off passenger %s at floor %s"

    COLUMN_NAMES = ["Elevator", "|", "Floor", "# people"]
    FLOOR_FORMAT = "{:>10}" * len(COLUMN_NAMES)
    ELEVATOR_ON_FLOOR = "X"
    NO_ELEVATOR = " "
    DELIMITER = "|"

    TIME_PER_FLOOR = 1.5
    STOP_TIME = 6

    def __init__(self,
                 num_floors,
                 total_passengers,
                 elevator_type,
                 capacity,
                 should_plot=True):
        self.num_floors = num_floors
        self.total_passengers = total_passengers
        self.elevator_type = elevator_type
        self.elevator_capacity = capacity
        self.should_plot = should_plot

        # Initialize passengers, elevator, and Building
        self.reset()

    def reset(self):
        """
        Initializes/resets the simulation to its initial state
        """
        # Generate a list of passengers based on total number of passengers
        self.passenger_list = [
            Passenger(i, self.num_floors)
            for i in range(1, self.total_passengers + 1)
        ]

        self.elevator = ElevatorFactory.get_elevator(self.num_floors,
                                                     self.elevator_type,
                                                     self.passenger_list,
                                                     self.elevator_capacity)

        self.building = Building(self.num_floors, self.passenger_list)

    def run(self):
        """
        Main method for simulation. The while True loop continues until
        all requests are fulfilled
        """
        self._print_state([], 0, True)
        total_wait_time = 0
        distance_moved = 0

        while True:
            # Used to print current state
            passenger_movements = []

            # Pick-up/drop-off passengers on current floor
            for passenger in self.elevator.requests:
                if passenger.at_destination:
                    continue

                # Add time waited to total
                total_wait_time += distance_moved * self.TIME_PER_FLOOR + self.STOP_TIME

                # Try pick-up
                pickup_move = self._pickup_passenger(passenger)
                # If passenger entered the elevator, store movement
                if pickup_move is not None:
                    passenger_movements.append(pickup_move)

                # Try drop-off
                dropoff_move = self._dropoff_passenger(passenger)
                # If passenger left the elevator, store movement
                if dropoff_move is not None:
                    passenger_movements.append(dropoff_move)

            # If there is no request left, we end the simulation and break the loop.
            if self.elevator.all_requests_completed():
                average_journey = 0
                if self.total_passengers != 0:
                    average_journey = total_wait_time / self.total_passengers

                if self.should_plot:
                    print(self.END_MESSAGE)
                    print(self.TIME_MESSAGE % (average_journey))

                return average_journey

            distance_moved = self.elevator.move()
            self._print_state(passenger_movements, distance_moved)

    def _pickup_passenger(self, passenger):
        """
        Pick-up passenger given that elevator is on the floor
        and the maximum capacity is not reached
        """
        if passenger.current == self.elevator.curr_floor \
                and passenger.in_elevator == False \
                and self.elevator.num_passengers < self.elevator.capacity:
            passenger.in_elevator = True
            self.elevator.num_passengers += 1

            return (self.PICKUP_MESSAGE %
                    (passenger.id, self.elevator.curr_floor))

        return None

    def _dropoff_passenger(self, passenger):
        """
        Drop-off passenger given that the destination is reached
        """
        if passenger.destination == self.elevator.curr_floor \
                and passenger.in_elevator == True:
            self.elevator.num_passengers -= 1
            passenger.at_destination = True

            return (self.DROPOFF_MESSAGE %
                    (passenger.id, self.elevator.curr_floor))

        return None

    def _print_state(self, passenger_movements, distance_moved, initial=False):
        """
        Prints the current state of the simulation
        """
        if not self.should_plot:
            return

        # Clear output
        os.system('clear')
        # clear_output(wait=True)

        # Illustration of current state
        print(self.FLOOR_FORMAT.format(*self.COLUMN_NAMES))

        for floor in range(self.num_floors - 1, -1, -1):
            floor_elements = []

            if floor == self.elevator.curr_floor:
                floor_elements.append(self.ELEVATOR_ON_FLOOR)
            else:
                floor_elements.append(self.NO_ELEVATOR)

            floor_elements.append(self.DELIMITER)
            floor_elements.append(floor)
            floor_elements.append(
                str(self.building.get_passengers_on_floor(floor)))

            print(self.FLOOR_FORMAT.format(*floor_elements))

        if initial:
            time.sleep(1)
            return

        print("=" * 50)
        print("Number of people in elevator while moving: %s" %
              self.elevator.num_passengers)
        print("Number of passengers who have received service: %s out of %s" %
              (self.elevator.requests_served(), len(self.elevator.requests)))

        # Display next elevator movement
        if self.elevator.is_moving_up():
            print(self.MOVE_MESSAGE %
                  (self.elevator.curr_floor - distance_moved,
                   self.elevator.curr_floor))
        elif not self.elevator.is_moving_up():
            print(self.MOVE_MESSAGE %
                  (self.elevator.curr_floor + distance_moved,
                   self.elevator.curr_floor))

        # Display passenger movements
        print("=" * 50)
        for movement in passenger_movements:
            print(movement)

        time.sleep(1)