############################################################
# simple Car class definition
############################################################

class Car:

    def __init__(
        self, license, color="Maroon", make="Ford",
        num_seats=5, passengers=None,
    ):
        self._color = color
        self._license = license
        self._make = make

        self._num_seats = num_seats
        if not passengers:
            self._passengers = []
        else:
            self._passengers = passengers

    def __str__(self):
        passengers = ", ".join(self._passengers) if self._passengers else "None"
        return "\n".join([
            f"{self._color} {self._make} (License: {self._license})",
            f"Seats: {self._num_seats}",
            f"Passengers: {passengers}",
        ])

    def add_passengers(self, passengers):
        """
        Add the given passengers to the car if there is enough available seating.

        If the total number of current and new passengers would exceed the car's
        seating capacity, no passengers are added.

        Parameters:
            passengers (list): Strings of names of passengers.
        """
        if len(self._passengers) + len(passengers) < self._num_seats:
            self._passengers.extend(passengers)


# EXERCISE 1:
# Draw out the environment diagram for the following code:

# car1 = Car(license="BEAVER", color="Red")

# passengers1 = ["Alicia", "Deepta", "Jaclyn", "Dani"]
# car1.add_passengers(passengers1)
# print(str(car1) + "\n")

# EXERCISE 2:
# What will the following code print?

# car2 = Car(license="61000", color="Blue")

# passengers2 = ["Alicia", "Deepta", "Jaclyn", "Dani", "Andrew"]
# car2.add_passengers(passengers2)
# print(str(car2) + "\n")

# EXERCISE 3:
# How can we modify the add_passengers() function to gain clarity on why no passengers were added?
# Now what will the following code print?

# car3 = Car(license="IHTFP", color="Gray")

# passengers3 = ["Alicia", "Deepta", "Jaclyn", "Dani", "Andrew"]
# car3.add_passengers(passengers3)
# print(str(car3) + "\n")


############################################################
# more realistic Car class definition
############################################################

class Car:

    def __init__(
        self, license, color="Maroon", make="Ford",
        odometer=0, fuel_level=0, tank_capacity=15,
        num_seats=4, passengers=None,
    ):
        self._color = color
        self._license = license
        self._make = make

        self._odometer = odometer
        self._fuel_level = fuel_level  # gallons
        self._tank_capacity = tank_capacity  # max gallons

        self._num_seats = num_seats
        self._passengers = passengers if passengers else []

    def __str__(self):
        passengers = ", ".join(self._passengers) if self._passengers else "None"
        return "\n".join([
            f"{self._color} {self._make} (License: {self._license})",
            f"Odometer: {self._odometer} miles",
            f"Fuel Level: {self._fuel_level:.2f} / {self._tank_capacity} gallons",
            f"Seats: {self._num_seats}",
            f"Passengers: {passengers}",
        ])

    def update_odometer(self, distance):
        """
        Update odometer with distance traveled

        Parameters:
            distance (int): The number of miles the car traveled.
        """
        self._odometer += distance

    def fill_tank(self):
        """Fill tank to maximum capacity."""
        self._fuel_level = self._tank_capacity

    def use_tank(self, gallons_used):
        """
        Reduce fuel level by a given number of gallons.
        """
        self._fuel_level = max(0, self._fuel_level - gallons_used)

    def add_passengers(self, passengers):
        """
        Add the given passengers to the car if there is enough available seating.

        If the total number of current and new passengers would exceed the car's
        seating capacity, no passengers are added.

        Parameters:
            passengers (list): Strings of names of passengers.
        """
        if len(self._passengers) + len(passengers) < self._num_seats:
            self._passengers.extend(passengers)
        else:
            print(f"Car {self._license} is at capacity: {self._num_seats}")

    def drive(self, distance, tank_loss_fn):
        """
        Drive the car for a given distance, updating the odometer and fuel level.

        Parameters:
            distance (int): The number of miles the car is driven.
            tank_loss_fn (function): Takes the distance and returns the proportion of fuel consumed.
        """
        self.use_tank(tank_loss_fn(distance))
        self.update_odometer(distance)

    # SOLUTION (EXERCISE 5)
    # def drive(self, distance, tank_loss_fn):
    #     """
    #     Drive the car for a given distance if there is enough fuel, updating the odometer and fuel level.
    #     If there is not enough fuel, drive as far as possible with the remaining fuel.

    #     Parameters:
    #         distance (int): The number of miles the car is driven.
    #         tank_loss_fn (function): Takes the distance and returns the proportion of fuel consumed.
    #     """
    #     fuel_needed = tank_loss_fn(distance)

    #     try:
    #         self.use_tank(fuel_needed)
    #         self.update_odometer(distance)
    #     except ValueError:
    #         fuel_available = self._fuel_level

    #         low, high = 0, distance
    #         max_distance = 0

    #         while low <= high:
    #             mid = (low + high) // 2
    #             if tank_loss_fn(mid) <= fuel_available:
    #                 max_distance = mid
    #                 low = mid + 1
    #             else:
    #                 high = mid - 1

    #         self._fuel_level = 0
    #         self.update_odometer(max_distance)

    #         print(f"Only drove {max_distance} miles due to limited fuel.")


############################################################
# External functions
############################################################


def loss_fn(distance):
    """
    Return amount of fuel lost based on distance using piecewise MPG.

    Parameters:
        distance (float): The number of miles the car is driven.
    """

    if distance <= 10:
        mpg = 18
    elif distance <= 30:
        mpg = 24
    else:
        mpg = 30

    return distance / mpg


############################################################
# Exception handling
############################################################


car4 = Car(license="123456", color="Yellow", fuel_level=10, tank_capacity=15)
print("Before driving: \n" + str(car4) + "\n")
car4.drive(50, loss_fn)
print("After driving: \n" + str(car4) + "\n")

# attempting to drive such that we exceed our current fuel level
car4.drive(500, loss_fn)
print("After driving: \n" + str(car4) + "\n")

# EXERCISE 4
# Rewrite use_tank() to raise an exception instead of returing 0.
# Where should we handle this exception?

# EXERCISE 5
# Rewrite drive() to handle the exception thrown by use_tank().
# Your function should drive the car as far as it can given the current fuel level.
